Merge cherrypicks of [2454758, 2454859, 2454837, 2454965, 2454984, 2455001, 2454760, 2454860, 2454838, 2454899, 2454970, 2455005, 2455027, 2454973, 2455006, 2455061, 2455007, 2454914, 2454987, 2454974] into oc-release

Change-Id: If5de23e18cf90ba248db649e8ba1981a5497ac59
diff --git a/BUILD b/BUILD
index a836f2f..915d368 100644
--- a/BUILD
+++ b/BUILD
@@ -24,9 +24,13 @@
 )
 
 java_test(
-    name = "ApkUtilsTest",
-    srcs = [
-        "src/test/java/com/android/apksig/apk/ApkUtilsTest.java",
-    ],
+    name = "all",
+    srcs = glob([
+        "src/test/java/com/android/apksig/**/*.java",
+    ]),
+    resources = glob([
+        "src/test/resources/**/*",
+    ]),
+    test_class = "com.android.apksig.AllTests",
     deps = [":apksig"],
 )
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..0b8d398
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,2 @@
+cbrubaker@google.com
+klyubin@google.com
diff --git a/etc/apksigner.bat b/etc/apksigner.bat
index 024f4e1..360e142 100755
--- a/etc/apksigner.bat
+++ b/etc/apksigner.bat
@@ -59,19 +59,19 @@
 

 :firstArg

 if [%1]==[] goto endArgs

-set a=%~1

+set "a=%~1"

 

     if [%defaultXmx%]==[] goto notXmx

-    if %a:~0,5% NEQ -JXmx goto notXmx

+    if "%a:~0,5%" NEQ "-JXmx" goto notXmx

         set defaultXmx=

     :notXmx

 

     if [%defaultXss%]==[] goto notXss

-    if %a:~0,5% NEQ -JXss goto notXss

+    if "%a:~0,5%" NEQ "-JXss" goto notXss

         set defaultXss=

     :notXss

 

-    if %a:~0,2% NEQ -J goto notJ

+    if "%a:~0,2%" NEQ "-J" goto notJ

         set javaOpts=%javaOpts% -%a:~2%

         shift /1

         goto firstArg

diff --git a/src/apksigner/java/com/android/apksigner/ApkSignerTool.java b/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
index 06b5603..9aed804 100644
--- a/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
+++ b/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
@@ -41,6 +41,7 @@
 import java.security.PrivateKey;
 import java.security.Provider;
 import java.security.PublicKey;
+import java.security.Security;
 import java.security.UnrecoverableKeyException;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateFactory;
@@ -67,7 +68,7 @@
  */
 public class ApkSignerTool {
 
-    private static final String VERSION = "0.5";
+    private static final String VERSION = "0.7";
     private static final String HELP_PAGE_GENERAL = "help.txt";
     private static final String HELP_PAGE_SIGN = "help_sign.txt";
     private static final String HELP_PAGE_VERIFY = "help_verify.txt";
@@ -122,6 +123,8 @@
         int maxSdkVersion = Integer.MAX_VALUE;
         List<SignerParams> signers = new ArrayList<>(1);
         SignerParams signerParams = new SignerParams();
+        List<ProviderInstallSpec> providers = new ArrayList<>();
+        ProviderInstallSpec providerParams = new ProviderInstallSpec();
         OptionsParser optionsParser = new OptionsParser(params);
         String optionName;
         String optionOriginalForm = null;
@@ -179,6 +182,20 @@
                 signerParams.certFile = optionsParser.getRequiredValue("Certificate file");
             } else if (("v".equals(optionName)) || ("verbose".equals(optionName))) {
                 verbose = optionsParser.getOptionalBooleanValue(true);
+            } else if ("next-provider".equals(optionName)) {
+                if (!providerParams.isEmpty()) {
+                    providers.add(providerParams);
+                    providerParams = new ProviderInstallSpec();
+                }
+            } else if ("provider-class".equals(optionName)) {
+                providerParams.className =
+                        optionsParser.getRequiredValue("JCA Provider class name");
+            } else if ("provider-arg".equals(optionName)) {
+                providerParams.constructorParam =
+                        optionsParser.getRequiredValue("JCA Provider constructor argument");
+            } else if ("provider-pos".equals(optionName)) {
+                providerParams.position =
+                        optionsParser.getRequiredIntValue("JCA Provider position");
             } else {
                 throw new ParameterException(
                         "Unsupported option: " + optionOriginalForm + ". See --help for supported"
@@ -189,6 +206,10 @@
             signers.add(signerParams);
         }
         signerParams = null;
+        if (!providerParams.isEmpty()) {
+            providers.add(providerParams);
+        }
+        providerParams = null;
 
         if (signers.isEmpty()) {
             throw new ParameterException("At least one signer must be specified");
@@ -219,6 +240,11 @@
                             + ")");
         }
 
+        // Install additional JCA Providers
+        for (ProviderInstallSpec providerInstallSpec : providers) {
+            providerInstallSpec.installProvider();
+        }
+
         List<ApkSigner.SignerConfig> signerConfigs = new ArrayList<>(signers.size());
         int signerNumber = 0;
         try (PasswordRetriever passwordRetriever = new PasswordRetriever()) {
@@ -531,6 +557,46 @@
         }
     }
 
+    private static class ProviderInstallSpec {
+        String className;
+        String constructorParam;
+        Integer position;
+
+        private boolean isEmpty() {
+            return (className == null) && (constructorParam == null) && (position == null);
+        }
+
+        private void installProvider() throws Exception {
+            if (className == null) {
+                throw new ParameterException(
+                        "JCA Provider class name (--provider-class) must be specified");
+            }
+
+            Class<?> providerClass = Class.forName(className);
+            if (!Provider.class.isAssignableFrom(providerClass)) {
+                throw new ParameterException(
+                        "JCA Provider class " + providerClass + " not subclass of "
+                                + Provider.class.getName());
+            }
+            Provider provider;
+            if (constructorParam != null) {
+                // Single-arg Provider constructor
+                provider =
+                        (Provider) providerClass.getConstructor(String.class)
+                                .newInstance(constructorParam);
+            } else {
+                // No-arg Provider constructor
+                provider = (Provider) providerClass.getConstructor().newInstance();
+            }
+
+            if (position == null) {
+                Security.addProvider(provider);
+            } else {
+                Security.insertProviderAt(provider, position);
+            }
+        }
+    }
+
     private static class SignerParams {
         String name;
 
@@ -623,17 +689,18 @@
             }
 
             // 2. Load the KeyStore
-            List<char[]> keystorePasswords = null;
-            if ("NONE".equals(keystoreFile)) {
-                ks.load(null);
-            } else {
+            List<char[]> keystorePasswords;
+            {
                 String keystorePasswordSpec =
                         (this.keystorePasswordSpec != null)
                                 ?  this.keystorePasswordSpec : PasswordRetriever.SPEC_STDIN;
                 keystorePasswords =
                         passwordRetriever.getPasswords(
                                 keystorePasswordSpec, "Keystore password for " + name);
-                loadKeyStoreFromFile(ks, keystoreFile, keystorePasswords);
+                loadKeyStoreFromFile(
+                        ks,
+                        "NONE".equals(keystoreFile) ? null : keystoreFile,
+                        keystorePasswords);
             }
 
             // 3. Load the PrivateKey and cert chain from KeyStore
@@ -725,13 +792,23 @@
             }
         }
 
+        /**
+         * Loads the password-protected keystore from storage.
+         *
+         * @param file file backing the keystore or {@code null} if the keystore is not file-backed,
+         *        for example, a PKCS #11 KeyStore.
+         */
         private static void loadKeyStoreFromFile(KeyStore ks, String file, List<char[]> passwords)
                 throws Exception {
             Exception lastFailure = null;
             for (char[] password : passwords) {
                 try {
-                    try (FileInputStream in = new FileInputStream(file)) {
-                        ks.load(in, password);
+                    if (file != null) {
+                        try (FileInputStream in = new FileInputStream(file)) {
+                            ks.load(in, password);
+                        }
+                    } else {
+                        ks.load(null, password);
                     }
                     return;
                 } catch (Exception e) {
diff --git a/src/apksigner/java/com/android/apksigner/help_sign.txt b/src/apksigner/java/com/android/apksigner/help_sign.txt
index ad865fe..11014e0 100644
--- a/src/apksigner/java/com/android/apksigner/help_sign.txt
+++ b/src/apksigner/java/com/android/apksigner/help_sign.txt
@@ -134,6 +134,25 @@
                       must be in X.509 PEM or DER format.
 
 
+        JCA PROVIDER INSTALLATION OPTIONS
+These options enable you to install additional Java Crypto Architecture (JCA)
+Providers, such PKCS #11 providers. Use --next-provider to delimit options of
+different providers. Providers are installed in the order in which they appear
+on the command-line.
+
+--provider-class      Fully-qualified class name of the JCA Provider.
+
+--provider-arg        Value to pass into the constructor of the JCA Provider
+                      class specified by --provider-class. The value is passed
+                      into the constructor as java.lang.String. By default, the
+                      no-arg provider's constructor is used.
+
+--provider-pos        Position / priority at which to install this provider in
+                      the JCA provider list. By default, the provider is
+                      installed as the lowest priority provider.
+                      See java.security.Security.insertProviderAt.
+
+
         EXAMPLES
 
 1. Sign an APK, in-place, using the one and only key in keystore release.jks:
@@ -148,3 +167,7 @@
 
 4. Sign an APK using two keys:
 $ apksigner sign --ks release.jks --next-signer --ks magic.jks app.apk
+
+5. Sign an APK using PKCS #11 JCA Provider:
+$ apksigner sign --provider-class sun.security.pkcs11.SunPKCS11 \
+    --provider-arg token.cfg --ks NONE --ks-type PKCS11 app.apk
diff --git a/src/main/java/com/android/apksig/ApkSigner.java b/src/main/java/com/android/apksig/ApkSigner.java
index 39f02db..2b7a7b6 100644
--- a/src/main/java/com/android/apksig/ApkSigner.java
+++ b/src/main/java/com/android/apksig/ApkSigner.java
@@ -690,12 +690,12 @@
     }
 
     /**
-     * Returns the minimum Android version (API Level) supported by the provided APK. This is based
-     * on the {@code android:minSdkVersion} attributes of the APK's {@code AndroidManifest.xml}.
+     * Returns the contents of the APK's {@code AndroidManifest.xml} or {@code null} if this entry
+     * is not present in the APK.
      */
-    static int getMinSdkVersionFromApk(
+    static ByteBuffer getAndroidManifestFromApk(
             List<CentralDirectoryRecord> cdRecords, DataSource lhfSection)
-                    throws IOException, MinSdkVersionException {
+                    throws IOException, ApkFormatException, ZipFormatException {
         CentralDirectoryRecord androidManifestCdRecord = null;
         for (CentralDirectoryRecord cdRecord : cdRecords) {
             if (ANDROID_MANIFEST_ZIP_ENTRY_NAME.equals(cdRecord.getName())) {
@@ -704,22 +704,30 @@
             }
         }
         if (androidManifestCdRecord == null) {
-            throw new MinSdkVersionException(
-                    "Unable to determine APK's minimum supported Android platform version"
-                            + ": APK is missing " + ANDROID_MANIFEST_ZIP_ENTRY_NAME);
+            throw new ApkFormatException("Missing " + ANDROID_MANIFEST_ZIP_ENTRY_NAME);
         }
-        byte[] androidManifest;
+
+        return ByteBuffer.wrap(
+                LocalFileRecord.getUncompressedData(
+                        lhfSection, androidManifestCdRecord, lhfSection.size()));
+    }
+
+    /**
+     * Returns the minimum Android version (API Level) supported by the provided APK. This is based
+     * on the {@code android:minSdkVersion} attributes of the APK's {@code AndroidManifest.xml}.
+     */
+    private static int getMinSdkVersionFromApk(
+            List<CentralDirectoryRecord> cdRecords, DataSource lhfSection)
+                    throws IOException, MinSdkVersionException {
+        ByteBuffer androidManifest;
         try {
-            androidManifest =
-                    LocalFileRecord.getUncompressedData(
-                            lhfSection, androidManifestCdRecord, lhfSection.size());
-        } catch (ZipFormatException e) {
+            androidManifest = getAndroidManifestFromApk(cdRecords, lhfSection);
+        } catch (ZipFormatException | ApkFormatException e) {
             throw new MinSdkVersionException(
-                    "Unable to determine APK's minimum supported Android platform version"
-                            + ": malformed ZIP entry: " + androidManifestCdRecord.getName(),
+                    "Failed to determine APK's minimum supported Android platform version",
                     e);
         }
-        return ApkUtils.getMinSdkVersionFromBinaryAndroidManifest(ByteBuffer.wrap(androidManifest));
+        return ApkUtils.getMinSdkVersionFromBinaryAndroidManifest(androidManifest);
     }
 
     /**
diff --git a/src/main/java/com/android/apksig/ApkVerifier.java b/src/main/java/com/android/apksig/ApkVerifier.java
index 8a0ffae..bf3997a 100644
--- a/src/main/java/com/android/apksig/ApkVerifier.java
+++ b/src/main/java/com/android/apksig/ApkVerifier.java
@@ -18,7 +18,7 @@
 
 import com.android.apksig.apk.ApkFormatException;
 import com.android.apksig.apk.ApkUtils;
-import com.android.apksig.apk.MinSdkVersionException;
+import com.android.apksig.internal.apk.AndroidBinXmlParser;
 import com.android.apksig.internal.apk.v1.V1SchemeVerifier;
 import com.android.apksig.internal.apk.v2.ContentDigestAlgorithm;
 import com.android.apksig.internal.apk.v2.SignatureAlgorithm;
@@ -32,6 +32,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
 import java.security.NoSuchAlgorithmException;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509Certificate;
@@ -135,6 +136,8 @@
                                 + ")");
             }
         }
+        int maxSdkVersion = mMaxSdkVersion;
+
         ApkUtils.ZipSections zipSections;
         try {
             zipSections = ApkUtils.findZipSections(apk);
@@ -142,30 +145,6 @@
             throw new ApkFormatException("Malformed APK: not a ZIP archive", e);
         }
 
-        int minSdkVersion;
-        if (mMinSdkVersion != null) {
-            // No need to obtain minSdkVersion from the APK's AndroidManifest.xml
-            minSdkVersion = mMinSdkVersion;
-        } else {
-            // Need to obtain minSdkVersion from the APK's AndroidManifest.xml
-            List<CentralDirectoryRecord> cdRecords;
-            try {
-                cdRecords = V1SchemeVerifier.parseZipCentralDirectory(apk, zipSections);
-            } catch (ApkFormatException e) {
-                throw new MinSdkVersionException(
-                        "Unable to determine APK's minimum supported Android platform version", e);
-            }
-            minSdkVersion =
-                    ApkSigner.getMinSdkVersionFromApk(
-                            cdRecords, apk.slice(0, zipSections.getZipCentralDirectoryOffset()));
-            if (minSdkVersion > mMaxSdkVersion) {
-                throw new IllegalArgumentException(
-                        "minSdkVersion from APK (" + minSdkVersion + ") > maxSdkVersion ("
-                                + mMaxSdkVersion + ")");
-            }
-        }
-        int maxSdkVersion = mMaxSdkVersion;
-
         Result result = new Result();
 
         // Android N and newer attempts to verify APK Signature Scheme v2 signature of the APK.
@@ -186,6 +165,43 @@
             foundApkSigSchemeIds = Collections.emptySet();
         }
 
+        ByteBuffer androidManifest = null;
+
+        int minSdkVersion;
+        if (mMinSdkVersion != null) {
+            // No need to obtain minSdkVersion from the APK's AndroidManifest.xml
+            minSdkVersion = mMinSdkVersion;
+        } else {
+            // Need to obtain minSdkVersion from the APK's AndroidManifest.xml
+            if (androidManifest == null) {
+                androidManifest = getAndroidManifestFromApk(apk, zipSections);
+            }
+            minSdkVersion =
+                    ApkUtils.getMinSdkVersionFromBinaryAndroidManifest(androidManifest.slice());
+            if (minSdkVersion > mMaxSdkVersion) {
+                throw new IllegalArgumentException(
+                        "minSdkVersion from APK (" + minSdkVersion + ") > maxSdkVersion ("
+                                + mMaxSdkVersion + ")");
+            }
+        }
+
+        // Android O and newer requires that APKs targeting security sandbox version 2 and higher
+        // are signed using APK Signature Scheme v2 or newer.
+        if (maxSdkVersion >= AndroidSdkVersion.O) {
+            if (androidManifest == null) {
+                androidManifest = getAndroidManifestFromApk(apk, zipSections);
+            }
+            int targetSandboxVersion =
+                    getTargetSandboxVersionFromBinaryAndroidManifest(androidManifest.slice());
+            if (targetSandboxVersion > 1) {
+                if (foundApkSigSchemeIds.isEmpty()) {
+                    result.addError(
+                            Issue.NO_SIG_FOR_TARGET_SANDBOX_VERSION,
+                            targetSandboxVersion);
+                }
+            }
+        }
+
         // Attempt to verify the APK using JAR signing if necessary. Platforms prior to Android N
         // ignore APK Signature Scheme v2 signatures and always attempt to verify JAR signatures.
         // Android N onwards verifies JAR signatures only if no APK Signature Scheme v2 (or newer
@@ -272,6 +288,83 @@
         return result;
     }
 
+    private static ByteBuffer getAndroidManifestFromApk(
+            DataSource apk, ApkUtils.ZipSections zipSections)
+                    throws IOException, ApkFormatException {
+        List<CentralDirectoryRecord> cdRecords =
+                V1SchemeVerifier.parseZipCentralDirectory(apk, zipSections);
+        try {
+            return ApkSigner.getAndroidManifestFromApk(
+                    cdRecords,
+                    apk.slice(0, zipSections.getZipCentralDirectoryOffset()));
+        } catch (ZipFormatException e) {
+            throw new ApkFormatException("Failed to read AndroidManifest.xml", e);
+        }
+    }
+
+    /**
+     * Android resource ID of the {@code android:targetSandboxVersion} attribute in
+     * AndroidManifest.xml.
+     */
+    private static final int TARGET_SANDBOX_VERSION_ATTR_ID = 0x0101054c;
+
+    /**
+     * Returns the security sandbox version targeted by an APK with the provided
+     * {@code AndroidManifest.xml}.
+     *
+     * @param androidManifestContents contents of {@code AndroidManifest.xml} in binary Android
+     *        resource format
+     *
+     * @throws ApkFormatException if an error occurred while determining the version
+     */
+    private static int getTargetSandboxVersionFromBinaryAndroidManifest(
+            ByteBuffer androidManifestContents) throws ApkFormatException {
+        // Return the value of the android:targetSandboxVersion attribute of the top-level manifest
+        // element
+        try {
+            AndroidBinXmlParser parser = new AndroidBinXmlParser(androidManifestContents);
+            int eventType = parser.getEventType();
+            while (eventType != AndroidBinXmlParser.EVENT_END_DOCUMENT) {
+                if ((eventType == AndroidBinXmlParser.EVENT_START_ELEMENT)
+                        && (parser.getDepth() == 1)
+                        && ("manifest".equals(parser.getName()))
+                        && (parser.getNamespace().isEmpty())) {
+                    // In each manifest element, targetSandboxVersion defaults to 1
+                    int result = 1;
+                    for (int i = 0; i < parser.getAttributeCount(); i++) {
+                        if (parser.getAttributeNameResourceId(i)
+                                == TARGET_SANDBOX_VERSION_ATTR_ID) {
+                            int valueType = parser.getAttributeValueType(i);
+                            switch (valueType) {
+                                case AndroidBinXmlParser.VALUE_TYPE_INT:
+                                    result = parser.getAttributeIntValue(i);
+                                    break;
+                                default:
+                                    throw new ApkFormatException(
+                                            "Failed to determine APK's target sandbox version"
+                                                    + ": unsupported value type of"
+                                                    + " AndroidManifest.xml"
+                                                    + " android:targetSandboxVersion"
+                                                    + ". Only integer values supported.");
+                            }
+                            break;
+                        }
+                    }
+                    return result;
+                }
+                eventType = parser.next();
+            }
+            throw new ApkFormatException(
+                    "Failed to determine APK's target sandbox version"
+                            + " : no manifest element in AndroidManifest.xml");
+        } catch (AndroidBinXmlParser.XmlParserException e) {
+            throw new ApkFormatException(
+                    "Failed to determine APK's target sandbox version"
+                            + ": malformed AndroidManifest.xml",
+                    e);
+        }
+    }
+
     /**
      * Result of verifying an APKs signatures. The APK can be considered verified iff
      * {@link #isVerified()} returns {@code true}.
@@ -353,6 +446,10 @@
             return mV2SchemeSigners;
         }
 
+        void addError(Issue msg, Object... parameters) {
+            mErrors.add(new IssueWithParams(msg, parameters));
+        }
+
         /**
          * Returns errors encountered while verifying the APK's signatures.
          */
@@ -897,6 +994,18 @@
         JAR_SIG_MISSING("No JAR signature from this signer"),
 
         /**
+         * APK is targeting a sandbox version which requires APK Signature Scheme v2 signature but
+         * no such signature was found.
+         *
+         * <ul>
+         * <li>Parameter 1: target sandbox version ({@code Integer})</li>
+         * </ul>
+         */
+        NO_SIG_FOR_TARGET_SANDBOX_VERSION(
+                "Missing APK Signature Scheme v2 signature required for target sandbox version"
+                        + " %1$d"),
+
+        /**
          * APK which is both JAR-signed and signed using APK Signature Scheme v2 contains a JAR
          * signature from this signer, but does not contain an APK Signature Scheme v2 signature
          * from this signer.
diff --git a/src/main/java/com/android/apksig/apk/ApkUtils.java b/src/main/java/com/android/apksig/apk/ApkUtils.java
index a5b221e..c4ada25 100644
--- a/src/main/java/com/android/apksig/apk/ApkUtils.java
+++ b/src/main/java/com/android/apksig/apk/ApkUtils.java
@@ -16,6 +16,7 @@
 
 package com.android.apksig.apk;
 
+import com.android.apksig.internal.apk.AndroidBinXmlParser;
 import com.android.apksig.internal.util.Pair;
 import com.android.apksig.internal.zip.ZipUtils;
 import com.android.apksig.util.DataSource;
diff --git a/src/main/java/com/android/apksig/apk/AndroidBinXmlParser.java b/src/main/java/com/android/apksig/internal/apk/AndroidBinXmlParser.java
similarity index 99%
rename from src/main/java/com/android/apksig/apk/AndroidBinXmlParser.java
rename to src/main/java/com/android/apksig/internal/apk/AndroidBinXmlParser.java
index 5c70ef1..447632c 100644
--- a/src/main/java/com/android/apksig/apk/AndroidBinXmlParser.java
+++ b/src/main/java/com/android/apksig/internal/apk/AndroidBinXmlParser.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.apksig.apk;
+package com.android.apksig.internal.apk;
 
 import java.io.UnsupportedEncodingException;
 import java.nio.ByteBuffer;
@@ -32,7 +32,7 @@
  * event can be obtained via an assortment of getters, for example, {@link #getName()} or
  * {@link #getAttributeNameResourceId(int)}.
  */
-class AndroidBinXmlParser {
+public class AndroidBinXmlParser {
 
     /** Event: start of document. */
     public static final int EVENT_START_DOCUMENT = 1;
@@ -817,7 +817,7 @@
     /**
      * Indicates that an error occurred while parsing a document.
      */
-    static class XmlParserException extends Exception {
+    public static class XmlParserException extends Exception {
         private static final long serialVersionUID = 1L;
 
         public XmlParserException(String message) {
diff --git a/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeSigner.java b/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeSigner.java
index af98007..0509d73 100644
--- a/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeSigner.java
+++ b/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeSigner.java
@@ -362,6 +362,7 @@
         SortedMap<String, byte[]> invidualSectionsContents = new TreeMap<>();
         String entryDigestAttributeName = getEntryDigestAttributeName(jarEntryDigestAlgorithm);
         for (String entryName : sortedEntryNames) {
+            checkEntryNameValid(entryName);
             byte[] entryDigest = jarEntryDigests.get(entryName);
             Attributes entryAttrs = new Attributes();
             entryAttrs.putValue(
@@ -386,6 +387,22 @@
         return result;
     }
 
+    private static void checkEntryNameValid(String name) throws ApkFormatException {
+        // JAR signing spec says CR, LF, and NUL are not permitted in entry names
+        // CR or LF in entry names will result in malformed MANIFEST.MF and .SF files because there
+        // is no way to escape characters in MANIFEST.MF and .SF files. NUL can, presumably, cause
+        // issues when parsing using C and C++ like languages.
+        for (char c : name.toCharArray()) {
+            if ((c == '\r') || (c == '\n') || (c == 0)) {
+                throw new ApkFormatException(
+                        String.format(
+                                "Unsupported character 0x%1$02x in ZIP entry name \"%2$s\"",
+                                (int) c,
+                                name));
+            }
+        }
+    }
+
     public static class OutputManifestFile {
         public byte[] contents;
         public SortedMap<String, byte[]> individualSectionsContents;
diff --git a/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeVerifier.java b/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeVerifier.java
index d7a4eb0..fdf459a 100644
--- a/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeVerifier.java
+++ b/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeVerifier.java
@@ -1425,18 +1425,18 @@
                 continue;
             }
 
-            Collection<NamedDigest> expectedDigests =
-                    getDigestsToVerify(manifestSection, "-Digest", minSdkVersion, maxSdkVersion);
+            List<NamedDigest> expectedDigests =
+                    new ArrayList<>(
+                            getDigestsToVerify(
+                                    manifestSection, "-Digest", minSdkVersion, maxSdkVersion));
             if (expectedDigests.isEmpty()) {
                 result.addError(Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_MANIFEST, entryName);
                 continue;
             }
 
             MessageDigest[] mds = new MessageDigest[expectedDigests.size()];
-            int mdIndex = 0;
-            for (NamedDigest expectedDigest : expectedDigests) {
-                mds[mdIndex] = getMessageDigest(expectedDigest.jcaDigestAlgorithm);
-                mdIndex++;
+            for (int i = 0; i < expectedDigests.size(); i++) {
+                mds[i] = getMessageDigest(expectedDigests.get(i).jcaDigestAlgorithm);
             }
 
             try {
@@ -1451,9 +1451,9 @@
                 throw new IOException("Failed to read entry: " + entryName, e);
             }
 
-            mdIndex = 0;
-            for (NamedDigest expectedDigest : expectedDigests) {
-                byte[] actualDigest = mds[mdIndex].digest();
+            for (int i = 0; i < expectedDigests.size(); i++) {
+                NamedDigest expectedDigest = expectedDigests.get(i);
+                byte[] actualDigest = mds[i].digest();
                 if (!Arrays.equals(expectedDigest.digest, actualDigest)) {
                     result.addError(
                             Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY,
diff --git a/src/main/java/com/android/apksig/internal/asn1/ber/BerDataValue.java b/src/main/java/com/android/apksig/internal/asn1/ber/BerDataValue.java
new file mode 100644
index 0000000..f5604ff
--- /dev/null
+++ b/src/main/java/com/android/apksig/internal/asn1/ber/BerDataValue.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.internal.asn1.ber;
+
+import java.nio.ByteBuffer;
+
+/**
+ * ASN.1 Basic Encoding Rules (BER) data value -- see {@code X.690}.
+ */
+public class BerDataValue {
+    private final ByteBuffer mEncoded;
+    private final ByteBuffer mEncodedContents;
+    private final int mTagClass;
+    private final boolean mConstructed;
+    private final int mTagNumber;
+
+    BerDataValue(
+            ByteBuffer encoded,
+            ByteBuffer encodedContents,
+            int tagClass,
+            boolean constructed,
+            int tagNumber) {
+        mEncoded = encoded;
+        mEncodedContents = encodedContents;
+        mTagClass = tagClass;
+        mConstructed = constructed;
+        mTagNumber = tagNumber;
+    }
+
+    /**
+     * Returns the tag class of this data value. See {@link BerEncoding} {@code TAG_CLASS}
+     * constants.
+     */
+    public int getTagClass() {
+        return mTagClass;
+    }
+
+    /**
+     * Returns {@code true} if the content octets of this data value are the complete BER encoding
+     * of one or more data values, {@code false} if the content octets of this data value directly
+     * represent the value.
+     */
+    public boolean isConstructed() {
+        return mConstructed;
+    }
+
+    /**
+     * Returns the tag number of this data value. See {@link BerEncoding} {@code TAG_NUMBER}
+     * constants.
+     */
+    public int getTagNumber() {
+        return mTagNumber;
+    }
+
+    /**
+     * Returns the encoded form of this data value.
+     */
+    public ByteBuffer getEncoded() {
+        return mEncoded.slice();
+    }
+
+    /**
+     * Returns the encoded contents of this data value.
+     */
+    public ByteBuffer getEncodedContents() {
+        return mEncodedContents.slice();
+    }
+
+    /**
+     * Returns a new reader of the contents of this data value.
+     */
+    public BerDataValueReader contentsReader() {
+        return new ByteBufferBerDataValueReader(getEncodedContents());
+    }
+
+    /**
+     * Returns a new reader which returns just this data value. This may be useful for re-reading
+     * this value in different contexts.
+     */
+    public BerDataValueReader dataValueReader() {
+        return new ParsedValueReader(this);
+    }
+
+    private static final class ParsedValueReader implements BerDataValueReader {
+        private final BerDataValue mValue;
+        private boolean mValueOutput;
+
+        public ParsedValueReader(BerDataValue value) {
+            mValue = value;
+        }
+
+        @Override
+        public BerDataValue readDataValue() throws BerDataValueFormatException {
+            if (mValueOutput) {
+                return null;
+            }
+            mValueOutput = true;
+            return mValue;
+        }
+    }
+}
diff --git a/src/main/java/com/android/apksig/internal/asn1/ber/BerDataValueFormatException.java b/src/main/java/com/android/apksig/internal/asn1/ber/BerDataValueFormatException.java
new file mode 100644
index 0000000..11ef6c3
--- /dev/null
+++ b/src/main/java/com/android/apksig/internal/asn1/ber/BerDataValueFormatException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.internal.asn1.ber;
+
+/**
+ * Indicates that an ASN.1 data value being read could not be decoded using
+ * Basic Encoding Rules (BER).
+ */
+public class BerDataValueFormatException extends Exception {
+
+    private static final long serialVersionUID = 1L;
+
+    public BerDataValueFormatException(String message) {
+        super(message);
+    }
+
+    public BerDataValueFormatException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/src/main/java/com/android/apksig/internal/asn1/ber/BerDataValueReader.java b/src/main/java/com/android/apksig/internal/asn1/ber/BerDataValueReader.java
new file mode 100644
index 0000000..eb2f383
--- /dev/null
+++ b/src/main/java/com/android/apksig/internal/asn1/ber/BerDataValueReader.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.internal.asn1.ber;
+
+/**
+ * Reader of ASN.1 Basic Encoding Rules (BER) data values.
+ *
+ * <p>BER data value reader returns data values, one by one, from a source. The interpretation of
+ * data values (e.g., how to obtain a numeric value from an INTEGER data value, or how to extract
+ * the elements of a SEQUENCE value) is left to clients of the reader.
+ */
+public interface BerDataValueReader {
+
+    /**
+     * Returns the next data value or {@code null} if end of input has been reached.
+     *
+     * @throws BerDataValueFormatExcepton if the value being read is malformed.
+     */
+    BerDataValue readDataValue() throws BerDataValueFormatException;
+}
diff --git a/src/main/java/com/android/apksig/internal/asn1/ber/BerEncoding.java b/src/main/java/com/android/apksig/internal/asn1/ber/BerEncoding.java
new file mode 100644
index 0000000..f893a65
--- /dev/null
+++ b/src/main/java/com/android/apksig/internal/asn1/ber/BerEncoding.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.internal.asn1.ber;
+
+/**
+ * ASN.1 Basic Encoding Rules (BER) constants and helper methods. See {@code X.690}.
+ */
+public abstract class BerEncoding {
+    private BerEncoding() {}
+
+    /**
+     * Constructed vs primitive flag in the first identifier byte.
+     */
+    public static final int ID_FLAG_CONSTRUCTED_ENCODING = 1 << 5;
+
+    /**
+     * Tag class: UNIVERSAL
+     */
+    public static final int TAG_CLASS_UNIVERSAL = 0;
+
+    /**
+     * Tag class: APPLICATION
+     */
+    public static final int TAG_CLASS_APPLICATION = 1;
+
+    /**
+     * Tag class: CONTEXT SPECIFIC
+     */
+    public static final int TAG_CLASS_CONTEXT_SPECIFIC = 2;
+
+    /**
+     * Tag class: PRIVATE
+     */
+    public static final int TAG_CLASS_PRIVATE = 3;
+
+    /**
+     * Tag number: INTEGER
+     */
+    public static final int TAG_NUMBER_INTEGER = 0x2;
+
+    /**
+     * Tag number: OCTET STRING
+     */
+    public static final int TAG_NUMBER_OCTET_STRING = 0x4;
+
+    /**
+     * Tag number: NULL
+     */
+    public static final int TAG_NUMBER_NULL = 0x05;
+
+    /**
+     * Tag number: OBJECT IDENTIFIER
+     */
+    public static final int TAG_NUMBER_OBJECT_IDENTIFIER = 0x6;
+
+    /**
+     * Tag number: SEQUENCE
+     */
+    public static final int TAG_NUMBER_SEQUENCE = 0x10;
+
+    /**
+     * Tag number: SET
+     */
+    public static final int TAG_NUMBER_SET = 0x11;
+
+    public static String tagNumberToString(int tagNumber) {
+        switch (tagNumber) {
+            case TAG_NUMBER_INTEGER:
+                return "INTEGER";
+            case TAG_NUMBER_OCTET_STRING:
+                return "OCTET STRING";
+            case TAG_NUMBER_NULL:
+                return "NULL";
+            case TAG_NUMBER_OBJECT_IDENTIFIER:
+                return "OBJECT IDENTIFIER";
+            case TAG_NUMBER_SEQUENCE:
+                return "SEQUENCE";
+            case TAG_NUMBER_SET:
+                return "SET";
+            default:
+                return "0x" + Integer.toHexString(tagNumber);
+        }
+    }
+
+    /**
+     * Returns {@code true} if the provided first identifier byte indicates that the data value uses
+     * constructed encoding for its contents, or {@code false} if the data value uses primitive
+     * encoding for its contents.
+     */
+    public static boolean isConstructed(byte firstIdentifierByte) {
+        return (firstIdentifierByte & ID_FLAG_CONSTRUCTED_ENCODING) != 0;
+    }
+
+    /**
+     * Returns the tag class encoded in the provided first identifier byte. See {@code TAG_CLASS}
+     * constants.
+     */
+    public static int getTagClass(byte firstIdentifierByte) {
+        return (firstIdentifierByte & 0xff) >> 6;
+    }
+
+    /**
+     * Returns the tag number encoded in the provided first identifier byte. See {@code TAG_NUMBER}
+     * constants.
+     */
+    public static int getTagNumber(byte firstIdentifierByte) {
+        return firstIdentifierByte & 0x1f;
+    }
+}
+
diff --git a/src/main/java/com/android/apksig/internal/asn1/ber/ByteBufferBerDataValueReader.java b/src/main/java/com/android/apksig/internal/asn1/ber/ByteBufferBerDataValueReader.java
new file mode 100644
index 0000000..adf2a25
--- /dev/null
+++ b/src/main/java/com/android/apksig/internal/asn1/ber/ByteBufferBerDataValueReader.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.internal.asn1.ber;
+
+import java.nio.ByteBuffer;
+
+/**
+ * {@link BerDataValueReader} which reads from a {@link ByteBuffer} containing BER-encoded data
+ * values. See {@code X.690} for the encoding.
+ */
+public class ByteBufferBerDataValueReader implements BerDataValueReader {
+    private final ByteBuffer mBuf;
+
+    public ByteBufferBerDataValueReader(ByteBuffer buf) {
+        if (buf == null) {
+            throw new NullPointerException("buf == null");
+        }
+        mBuf = buf;
+    }
+
+    @Override
+    public BerDataValue readDataValue() throws BerDataValueFormatException {
+        int startPosition = mBuf.position();
+        if (!mBuf.hasRemaining()) {
+            return null;
+        }
+        byte firstIdentifierByte = mBuf.get();
+        int tagNumber = readTagNumber(firstIdentifierByte);
+
+        if (!mBuf.hasRemaining()) {
+            throw new BerDataValueFormatException("Missing length");
+        }
+        int firstLengthByte = mBuf.get() & 0xff;
+        int contentsLength;
+        int contentsOffsetInTag;
+        if ((firstLengthByte & 0x80) == 0) {
+            // short form length
+            contentsLength = readShortFormLength(firstLengthByte);
+            contentsOffsetInTag = mBuf.position() - startPosition;
+            skipDefiniteLengthContents(contentsLength);
+        } else if (firstLengthByte != 0x80) {
+            // long form length
+            contentsLength = readLongFormLength(firstLengthByte);
+            contentsOffsetInTag = mBuf.position() - startPosition;
+            skipDefiniteLengthContents(contentsLength);
+        } else {
+            // indefinite length -- value ends with 0x00 0x00
+            contentsOffsetInTag = mBuf.position() - startPosition;
+            contentsLength = skipIndefiniteLengthContents();
+        }
+
+        // Create the encoded data value ByteBuffer
+        int endPosition = mBuf.position();
+        mBuf.position(startPosition);
+        int bufOriginalLimit = mBuf.limit();
+        mBuf.limit(endPosition);
+        ByteBuffer encoded = mBuf.slice();
+        mBuf.position(mBuf.limit());
+        mBuf.limit(bufOriginalLimit);
+
+        // Create the encoded contents ByteBuffer
+        encoded.position(contentsOffsetInTag);
+        encoded.limit(contentsOffsetInTag + contentsLength);
+        ByteBuffer encodedContents = encoded.slice();
+        encoded.clear();
+
+        return new BerDataValue(
+                encoded,
+                encodedContents,
+                BerEncoding.getTagClass(firstIdentifierByte),
+                BerEncoding.isConstructed(firstIdentifierByte),
+                tagNumber);
+    }
+
+    private int readTagNumber(byte firstIdentifierByte) throws BerDataValueFormatException {
+        int tagNumber = BerEncoding.getTagNumber(firstIdentifierByte);
+        if (tagNumber == 0x1f) {
+            // high-tag-number form, where the tag number follows this byte in base-128
+            // big-endian form, where each byte has the highest bit set, except for the last
+            // byte
+            return readHighTagNumber();
+        } else {
+            // low-tag-number form
+            return tagNumber;
+        }
+    }
+
+    private int readHighTagNumber() throws BerDataValueFormatException {
+        // Base-128 big-endian form, where each byte has the highest bit set, except for the last
+        // byte
+        int b;
+        int result = 0;
+        do {
+            if (!mBuf.hasRemaining()) {
+                throw new BerDataValueFormatException("Truncated tag number");
+            }
+            b = mBuf.get();
+            if (result > Integer.MAX_VALUE >>> 7) {
+                throw new BerDataValueFormatException("Tag number too large");
+            }
+            result <<= 7;
+            result |= b & 0x7f;
+        } while ((b & 0x80) != 0);
+        return result;
+    }
+
+    private int readShortFormLength(int firstLengthByte) throws BerDataValueFormatException {
+        return firstLengthByte & 0x7f;
+    }
+
+    private int readLongFormLength(int firstLengthByte) throws BerDataValueFormatException {
+        // The low 7 bits of the first byte represent the number of bytes (following the first
+        // byte) in which the length is in big-endian base-256 form
+        int byteCount = firstLengthByte & 0x7f;
+        if (byteCount > 4) {
+            throw new BerDataValueFormatException("Length too large: " + byteCount + " bytes");
+        }
+        int result = 0;
+        for (int i = 0; i < byteCount; i++) {
+            if (!mBuf.hasRemaining()) {
+                throw new BerDataValueFormatException("Truncated length");
+            }
+            int b = mBuf.get();
+            if (result > Integer.MAX_VALUE >>> 8) {
+                throw new BerDataValueFormatException("Length too large");
+            }
+            result <<= 8;
+            result |= b & 0xff;
+        }
+        return result;
+    }
+
+    private void skipDefiniteLengthContents(int contentsLength) throws BerDataValueFormatException {
+        if (mBuf.remaining() < contentsLength) {
+            throw new BerDataValueFormatException(
+                    "Truncated contents. Need: " + contentsLength + " bytes, available: "
+                            + mBuf.remaining());
+        }
+        mBuf.position(mBuf.position() + contentsLength);
+    }
+
+    private int skipIndefiniteLengthContents() throws BerDataValueFormatException {
+        // Contents are terminated by 0x00 0x00
+        boolean prevZeroByte = false;
+        int bytesRead = 0;
+        while (true) {
+            if (!mBuf.hasRemaining()) {
+                throw new BerDataValueFormatException(
+                        "Truncated indefinite-length contents: " + bytesRead + " bytes read");
+
+            }
+            int b = mBuf.get();
+            bytesRead++;
+            if (bytesRead < 0) {
+                throw new BerDataValueFormatException("Indefinite-length contents too long");
+            }
+            if (b == 0) {
+                if (prevZeroByte) {
+                    // End of contents reached -- we've read the value and its terminator 0x00 0x00
+                    return bytesRead - 2;
+                }
+                prevZeroByte = true;
+            } else {
+                prevZeroByte = false;
+            }
+        }
+    }
+}
diff --git a/src/main/java/com/android/apksig/internal/asn1/ber/InputStreamBerDataValueReader.java b/src/main/java/com/android/apksig/internal/asn1/ber/InputStreamBerDataValueReader.java
new file mode 100644
index 0000000..9dfec15
--- /dev/null
+++ b/src/main/java/com/android/apksig/internal/asn1/ber/InputStreamBerDataValueReader.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.internal.asn1.ber;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * {@link BerDataValueReader} which reads from an {@link InputStream} returning BER-encoded data
+ * values. See {@code X.690} for the encoding.
+ */
+public class InputStreamBerDataValueReader implements BerDataValueReader {
+    private final InputStream mIn;
+
+    public InputStreamBerDataValueReader(InputStream in) {
+        if (in == null) {
+            throw new NullPointerException("in == null");
+        }
+        mIn = in;
+    }
+
+    @SuppressWarnings("resource")
+    @Override
+    public BerDataValue readDataValue() throws BerDataValueFormatException {
+        RecordingInputStream in = new RecordingInputStream(mIn);
+
+        try {
+            int firstIdentifierByte = in.read();
+            if (firstIdentifierByte == -1) {
+                // End of input
+                return null;
+            }
+            int tagNumber = readTagNumber(in, firstIdentifierByte);
+
+            int firstLengthByte = in.read();
+            if (firstLengthByte == -1) {
+                throw new BerDataValueFormatException("Missing length");
+            }
+
+            int contentsLength;
+            int contentsOffsetInDataValue;
+            if ((firstLengthByte & 0x80) == 0) {
+                // short form length
+                contentsLength = readShortFormLength(firstLengthByte);
+                contentsOffsetInDataValue = in.getReadByteCount();
+                skipDefiniteLengthContents(in, contentsLength);
+            } else if ((firstLengthByte & 0xff) != 0x80) {
+                // long form length
+                contentsLength = readLongFormLength(in, firstLengthByte);
+                contentsOffsetInDataValue = in.getReadByteCount();
+                skipDefiniteLengthContents(in, contentsLength);
+            } else {
+                // indefinite length
+                contentsOffsetInDataValue = in.getReadByteCount();
+                contentsLength = skipIndefiniteLengthContents(in);
+            }
+
+            byte[] encoded = in.getReadBytes();
+            ByteBuffer encodedContents =
+                    ByteBuffer.wrap(encoded, contentsOffsetInDataValue, contentsLength);
+            return new BerDataValue(
+                    ByteBuffer.wrap(encoded),
+                    encodedContents,
+                    BerEncoding.getTagClass((byte) firstIdentifierByte),
+                    BerEncoding.isConstructed((byte) firstIdentifierByte),
+                    tagNumber);
+        } catch (IOException e) {
+            throw new BerDataValueFormatException("Failed to read data value", e);
+        }
+    }
+
+    private static int readTagNumber(InputStream in, int firstIdentifierByte)
+            throws IOException, BerDataValueFormatException {
+        int tagNumber = BerEncoding.getTagNumber((byte) firstIdentifierByte);
+        if (tagNumber == 0x1f) {
+            // high-tag-number form
+            return readHighTagNumber(in);
+        } else {
+            // low-tag-number form
+            return tagNumber;
+        }
+    }
+
+    private static int readHighTagNumber(InputStream in)
+            throws IOException, BerDataValueFormatException {
+        // Base-128 big-endian form, where each byte has the highest bit set, except for the last
+        // byte where the highest bit is not set
+        int b;
+        int result = 0;
+        do {
+            b = in.read();
+            if (b == -1) {
+                throw new BerDataValueFormatException("Truncated tag number");
+            }
+            if (result > Integer.MAX_VALUE >>> 7) {
+                throw new BerDataValueFormatException("Tag number too large");
+            }
+            result <<= 7;
+            result |= b & 0x7f;
+        } while ((b & 0x80) != 0);
+        return result;
+    }
+
+    private static int readShortFormLength(int firstLengthByte) throws BerDataValueFormatException {
+        return firstLengthByte & 0x7f;
+    }
+
+    private static int readLongFormLength(InputStream in, int firstLengthByte)
+            throws IOException, BerDataValueFormatException {
+        // The low 7 bits of the first byte represent the number of bytes (following the first
+        // byte) in which the length is in big-endian base-256 form
+        int byteCount = firstLengthByte & 0x7f;
+        if (byteCount > 4) {
+            throw new BerDataValueFormatException("Length too large: " + byteCount + " bytes");
+        }
+        int result = 0;
+        for (int i = 0; i < byteCount; i++) {
+            int b = in.read();
+            if (b == -1) {
+                throw new BerDataValueFormatException("Truncated length");
+            }
+            if (result > Integer.MAX_VALUE >>> 8) {
+                throw new BerDataValueFormatException("Length too large");
+            }
+            result <<= 8;
+            result |= b & 0xff;
+        }
+        return result;
+    }
+
+    private static void skipDefiniteLengthContents(InputStream in, int len)
+            throws IOException, BerDataValueFormatException {
+        long bytesRead = 0;
+        while (len > 0) {
+            int skipped = (int) in.skip(len);
+            if (skipped <= 0) {
+                throw new BerDataValueFormatException(
+                        "Truncated definite-length contents: " + bytesRead + " bytes read"
+                                + ", " + len + " missing");
+            }
+            len -= skipped;
+            bytesRead += skipped;
+        }
+    }
+
+    private static int skipIndefiniteLengthContents(InputStream in)
+            throws IOException, BerDataValueFormatException {
+        // Contents are terminated by 0x00 0x00
+        boolean prevZeroByte = false;
+        int bytesRead = 0;
+        while (true) {
+            int b = in.read();
+            if (b == -1) {
+                throw new BerDataValueFormatException(
+                        "Truncated indefinite-length contents: " + bytesRead + " bytes read");
+            }
+            bytesRead++;
+            if (bytesRead < 0) {
+                throw new BerDataValueFormatException("Indefinite-length contents too long");
+            }
+            if (b == 0) {
+                if (prevZeroByte) {
+                    // End of contents reached -- we've read the value and its terminator 0x00 0x00
+                    return bytesRead - 2;
+                }
+                prevZeroByte = true;
+                continue;
+            } else {
+                prevZeroByte = false;
+            }
+        }
+    }
+
+    private static class RecordingInputStream extends InputStream {
+        private final InputStream mIn;
+        private final ByteArrayOutputStream mBuf;
+
+        private RecordingInputStream(InputStream in) {
+            mIn = in;
+            mBuf = new ByteArrayOutputStream();
+        }
+
+        public byte[] getReadBytes() {
+            return mBuf.toByteArray();
+        }
+
+        public int getReadByteCount() {
+            return mBuf.size();
+        }
+
+        @Override
+        public int read() throws IOException {
+            int b = mIn.read();
+            if (b != -1) {
+                mBuf.write(b);
+            }
+            return b;
+        }
+
+        @Override
+        public int read(byte[] b) throws IOException {
+            int len = mIn.read(b);
+            if (len > 0) {
+                mBuf.write(b, 0, len);
+            }
+            return len;
+        }
+
+        @Override
+        public int read(byte[] b, int off, int len) throws IOException {
+            len = mIn.read(b, off, len);
+            if (len > 0) {
+                mBuf.write(b, off, len);
+            }
+            return len;
+        }
+
+        @Override
+        public long skip(long n) throws IOException {
+            if (n <= 0) {
+                return mIn.skip(n);
+            }
+
+            byte[] buf = new byte[4096];
+            int len = mIn.read(buf, 0, (int) Math.min(buf.length, n));
+            if (len > 0) {
+                mBuf.write(buf, 0, len);
+            }
+            return (len < 0) ? 0 : len;
+        }
+
+        @Override
+        public int available() throws IOException {
+            return super.available();
+        }
+
+        @Override
+        public void close() throws IOException {
+            super.close();
+        }
+
+        @Override
+        public synchronized void mark(int readlimit) {}
+
+        @Override
+        public synchronized void reset() throws IOException {
+            throw new IOException("mark/reset not supported");
+        }
+
+        @Override
+        public boolean markSupported() {
+            return false;
+        }
+    }
+}
diff --git a/src/main/java/com/android/apksig/internal/util/AndroidSdkVersion.java b/src/main/java/com/android/apksig/internal/util/AndroidSdkVersion.java
index f874f88..83d1334 100644
--- a/src/main/java/com/android/apksig/internal/util/AndroidSdkVersion.java
+++ b/src/main/java/com/android/apksig/internal/util/AndroidSdkVersion.java
@@ -35,4 +35,7 @@
 
     /** Android 7.0. N is for Nougat. */
     public static final int N = 24;
+
+    /** Android O. */
+    public static final int O = 26;
 }
diff --git a/src/main/java/com/android/apksig/internal/util/ByteArrayDataSink.java b/src/main/java/com/android/apksig/internal/util/ByteArrayDataSink.java
index f804fa2..e5741a5 100644
--- a/src/main/java/com/android/apksig/internal/util/ByteArrayDataSink.java
+++ b/src/main/java/com/android/apksig/internal/util/ByteArrayDataSink.java
@@ -47,6 +47,17 @@
 
     @Override
     public void consume(byte[] buf, int offset, int length) throws IOException {
+        if (offset < 0) {
+            // Must perform this check because System.arraycopy below doesn't perform it when
+            // length == 0
+            throw new IndexOutOfBoundsException("offset: " + offset);
+        }
+        if (offset > buf.length) {
+            // Must perform this check because System.arraycopy below doesn't perform it when
+            // length == 0
+            throw new IndexOutOfBoundsException(
+                    "offset: " + offset + ", buf.length: " + buf.length);
+        }
         if (length == 0) {
             return;
         }
@@ -106,7 +117,7 @@
         checkChunkValid(offset, size);
 
         // checkChunkValid ensures that it's OK to cast offset to int.
-        return ByteBuffer.wrap(mArray, (int) offset, size);
+        return ByteBuffer.wrap(mArray, (int) offset, size).slice();
     }
 
     @Override
@@ -127,22 +138,22 @@
 
     private void checkChunkValid(long offset, long size) {
         if (offset < 0) {
-            throw new IllegalArgumentException("offset: " + offset);
+            throw new IndexOutOfBoundsException("offset: " + offset);
         }
         if (size < 0) {
-            throw new IllegalArgumentException("size: " + size);
+            throw new IndexOutOfBoundsException("size: " + size);
         }
         if (offset > mSize) {
-            throw new IllegalArgumentException(
+            throw new IndexOutOfBoundsException(
                     "offset (" + offset + ") > source size (" + mSize + ")");
         }
         long endOffset = offset + size;
         if (endOffset < offset) {
-            throw new IllegalArgumentException(
+            throw new IndexOutOfBoundsException(
                     "offset (" + offset + ") + size (" + size + ") overflow");
         }
         if (endOffset > mSize) {
-            throw new IllegalArgumentException(
+            throw new IndexOutOfBoundsException(
                     "offset (" + offset + ") + size (" + size + ") > source size (" + mSize + ")");
         }
     }
@@ -184,7 +195,7 @@
             checkChunkValid(offset, size);
             // checkChunkValid combined with the way instances of this class are constructed ensures
             // that mSliceOffset + offset does not overflow.
-            return ByteBuffer.wrap(mArray, (int) (mSliceOffset + offset), size);
+            return ByteBuffer.wrap(mArray, (int) (mSliceOffset + offset), size).slice();
         }
 
         @Override
@@ -205,22 +216,22 @@
 
         private void checkChunkValid(long offset, long size) {
             if (offset < 0) {
-                throw new IllegalArgumentException("offset: " + offset);
+                throw new IndexOutOfBoundsException("offset: " + offset);
             }
             if (size < 0) {
-                throw new IllegalArgumentException("size: " + size);
+                throw new IndexOutOfBoundsException("size: " + size);
             }
             if (offset > mSliceSize) {
-                throw new IllegalArgumentException(
+                throw new IndexOutOfBoundsException(
                         "offset (" + offset + ") > source size (" + mSliceSize + ")");
             }
             long endOffset = offset + size;
             if (endOffset < offset) {
-                throw new IllegalArgumentException(
+                throw new IndexOutOfBoundsException(
                         "offset (" + offset + ") + size (" + size + ") overflow");
             }
             if (endOffset > mSliceSize) {
-                throw new IllegalArgumentException(
+                throw new IndexOutOfBoundsException(
                         "offset (" + offset + ") + size (" + size + ") > source size (" + mSliceSize
                                 + ")");
             }
diff --git a/src/main/java/com/android/apksig/internal/util/ByteBufferDataSource.java b/src/main/java/com/android/apksig/internal/util/ByteBufferDataSource.java
index 258a379..656c20e 100644
--- a/src/main/java/com/android/apksig/internal/util/ByteBufferDataSource.java
+++ b/src/main/java/com/android/apksig/internal/util/ByteBufferDataSource.java
@@ -82,7 +82,7 @@
     @Override
     public void feed(long offset, long size, DataSink sink) throws IOException {
         if ((size < 0) || (size > mSize)) {
-            throw new IllegalArgumentException("size: " + size + ", source size: " + mSize);
+            throw new IndexOutOfBoundsException("size: " + size + ", source size: " + mSize);
         }
         sink.consume(getByteBuffer(offset, (int) size));
     }
@@ -93,7 +93,7 @@
             return this;
         }
         if ((size < 0) || (size > mSize)) {
-            throw new IllegalArgumentException("size: " + size + ", source size: " + mSize);
+            throw new IndexOutOfBoundsException("size: " + size + ", source size: " + mSize);
         }
         return new ByteBufferDataSource(
                 getByteBuffer(offset, (int) size),
@@ -103,22 +103,22 @@
 
     private void checkChunkValid(long offset, long size) {
         if (offset < 0) {
-            throw new IllegalArgumentException("offset: " + offset);
+            throw new IndexOutOfBoundsException("offset: " + offset);
         }
         if (size < 0) {
-            throw new IllegalArgumentException("size: " + size);
+            throw new IndexOutOfBoundsException("size: " + size);
         }
         if (offset > mSize) {
-            throw new IllegalArgumentException(
+            throw new IndexOutOfBoundsException(
                     "offset (" + offset + ") > source size (" + mSize + ")");
         }
         long endOffset = offset + size;
         if (endOffset < offset) {
-            throw new IllegalArgumentException(
+            throw new IndexOutOfBoundsException(
                     "offset (" + offset + ") + size (" + size + ") overflow");
         }
         if (endOffset > mSize) {
-            throw new IllegalArgumentException(
+            throw new IndexOutOfBoundsException(
                     "offset (" + offset + ") + size (" + size + ") > source size (" + mSize  +")");
         }
     }
diff --git a/src/main/java/com/android/apksig/internal/util/ByteBufferSink.java b/src/main/java/com/android/apksig/internal/util/ByteBufferSink.java
index c81a38a..d7cbe03 100644
--- a/src/main/java/com/android/apksig/internal/util/ByteBufferSink.java
+++ b/src/main/java/com/android/apksig/internal/util/ByteBufferSink.java
@@ -32,6 +32,10 @@
         mBuffer = buffer;
     }
 
+    public ByteBuffer getBuffer() {
+        return mBuffer;
+    }
+
     @Override
     public void consume(byte[] buf, int offset, int length) throws IOException {
         try {
diff --git a/src/main/java/com/android/apksig/internal/util/RandomAccessFileDataSink.java b/src/main/java/com/android/apksig/internal/util/RandomAccessFileDataSink.java
index 0e075d7..bbd2d14 100644
--- a/src/main/java/com/android/apksig/internal/util/RandomAccessFileDataSink.java
+++ b/src/main/java/com/android/apksig/internal/util/RandomAccessFileDataSink.java
@@ -55,8 +55,26 @@
         mPosition = startPosition;
     }
 
+    /**
+     * Returns the underlying {@link RandomAccessFile}.
+     */
+    public RandomAccessFile getFile() {
+        return mFile;
+    }
+
     @Override
     public void consume(byte[] buf, int offset, int length) throws IOException {
+        if (offset < 0) {
+            // Must perform this check here because RandomAccessFile.write doesn't throw when offset
+            // is negative but length is 0
+            throw new IndexOutOfBoundsException("offset: " + offset);
+        }
+        if (offset > buf.length) {
+            // Must perform this check here because RandomAccessFile.write doesn't throw when offset
+            // is too large but length is 0
+            throw new IndexOutOfBoundsException(
+                    "offset: " + offset + ", buf.length: " + buf.length);
+        }
         if (length == 0) {
             return;
         }
diff --git a/src/main/java/com/android/apksig/internal/util/RandomAccessFileDataSource.java b/src/main/java/com/android/apksig/internal/util/RandomAccessFileDataSource.java
index 4104fb0..3492399 100644
--- a/src/main/java/com/android/apksig/internal/util/RandomAccessFileDataSource.java
+++ b/src/main/java/com/android/apksig/internal/util/RandomAccessFileDataSource.java
@@ -20,6 +20,7 @@
 import com.android.apksig.util.DataSource;
 import java.io.IOException;
 import java.io.RandomAccessFile;
+import java.nio.BufferOverflowException;
 import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
 
@@ -49,13 +50,15 @@
      * Constructs a new {@code RandomAccessFileDataSource} based on the data contained in the
      * specified region of the provided file. Changes to the contents of the file will be visible in
      * this data source.
+     *
+     * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative.
      */
     public RandomAccessFileDataSource(RandomAccessFile file, long offset, long size) {
         if (offset < 0) {
-            throw new IllegalArgumentException("offset: " + size);
+            throw new IndexOutOfBoundsException("offset: " + size);
         }
         if (size < 0) {
-            throw new IllegalArgumentException("size: " + size);
+            throw new IndexOutOfBoundsException("size: " + size);
         }
         mFile = file;
         mOffset = offset;
@@ -116,11 +119,16 @@
         if (size == 0) {
             return;
         }
+        if (size > dest.remaining()) {
+            throw new BufferOverflowException();
+        }
 
         long offsetInFile = mOffset + offset;
         int remaining = size;
         int prevLimit = dest.limit();
         try {
+            // FileChannel.read(ByteBuffer) reads up to dest.remaining(). Thus, we need to adjust
+            // the buffer's limit to avoid reading more than size bytes.
             dest.limit(dest.position() + size);
             FileChannel fileChannel = mFile.getChannel();
             while (remaining > 0) {
@@ -139,6 +147,9 @@
 
     @Override
     public ByteBuffer getByteBuffer(long offset, int size) throws IOException {
+        if (size < 0) {
+            throw new IndexOutOfBoundsException("size: " + size);
+        }
         ByteBuffer result = ByteBuffer.allocate(size);
         copyTo(offset, size, result);
         result.flip();
@@ -147,22 +158,22 @@
 
     private static void checkChunkValid(long offset, long size, long sourceSize) {
         if (offset < 0) {
-            throw new IllegalArgumentException("offset: " + offset);
+            throw new IndexOutOfBoundsException("offset: " + offset);
         }
         if (size < 0) {
-            throw new IllegalArgumentException("size: " + size);
+            throw new IndexOutOfBoundsException("size: " + size);
         }
         if (offset > sourceSize) {
-            throw new IllegalArgumentException(
+            throw new IndexOutOfBoundsException(
                     "offset (" + offset + ") > source size (" + sourceSize + ")");
         }
         long endOffset = offset + size;
         if (endOffset < offset) {
-            throw new IllegalArgumentException(
+            throw new IndexOutOfBoundsException(
                     "offset (" + offset + ") + size (" + size + ") overflow");
         }
         if (endOffset > sourceSize) {
-            throw new IllegalArgumentException(
+            throw new IndexOutOfBoundsException(
                     "offset (" + offset + ") + size (" + size
                             + ") > source size (" + sourceSize  +")");
         }
diff --git a/src/main/java/com/android/apksig/internal/zip/CentralDirectoryRecord.java b/src/main/java/com/android/apksig/internal/zip/CentralDirectoryRecord.java
index 9e6e119..d2f444d 100644
--- a/src/main/java/com/android/apksig/internal/zip/CentralDirectoryRecord.java
+++ b/src/main/java/com/android/apksig/internal/zip/CentralDirectoryRecord.java
@@ -38,11 +38,13 @@
     private static final int RECORD_SIGNATURE = 0x02014b50;
     private static final int HEADER_SIZE_BYTES = 46;
 
-    private static final int LAST_MODIFICATION_TIME_OFFSET =  12;
+    private static final int GP_FLAGS_OFFSET = 8;
     private static final int LOCAL_FILE_HEADER_OFFSET_OFFSET = 42;
     private static final int NAME_OFFSET = HEADER_SIZE_BYTES;
 
     private final ByteBuffer mData;
+    private final short mGpFlags;
+    private final short mCompressionMethod;
     private final int mLastModificationTime;
     private final int mLastModificationDate;
     private final long mCrc32;
@@ -54,6 +56,8 @@
 
     private CentralDirectoryRecord(
             ByteBuffer data,
+            short gpFlags,
+            short compressionMethod,
             int lastModificationTime,
             int lastModificationDate,
             long crc32,
@@ -63,6 +67,8 @@
             String name,
             int nameSizeBytes) {
         mData = data;
+        mGpFlags = gpFlags;
+        mCompressionMethod = compressionMethod;
         mLastModificationDate = lastModificationDate;
         mLastModificationTime = lastModificationTime;
         mCrc32 = crc32;
@@ -85,6 +91,14 @@
         return mNameSizeBytes;
     }
 
+    public short getGpFlags() {
+        return mGpFlags;
+    }
+
+    public short getCompressionMethod() {
+        return mCompressionMethod;
+    }
+
     public int getLastModificationTime() {
         return mLastModificationTime;
     }
@@ -128,7 +142,9 @@
                     "Not a Central Directory record. Signature: 0x"
                             + Long.toHexString(recordSignature & 0xffffffffL));
         }
-        buf.position(originalPosition + LAST_MODIFICATION_TIME_OFFSET);
+        buf.position(originalPosition + GP_FLAGS_OFFSET);
+        short gpFlags = buf.getShort();
+        short compressionMethod = buf.getShort();
         int lastModificationTime = ZipUtils.getUnsignedInt16(buf);
         int lastModificationDate = ZipUtils.getUnsignedInt16(buf);
         long crc32 = ZipUtils.getUnsignedInt32(buf);
@@ -162,6 +178,8 @@
         buf.position(recordEndInBuf);
         return new CentralDirectoryRecord(
                 recordBuf,
+                gpFlags,
+                compressionMethod,
                 lastModificationTime,
                 lastModificationDate,
                 crc32,
@@ -185,6 +203,8 @@
         ZipUtils.setUnsignedInt32(result, LOCAL_FILE_HEADER_OFFSET_OFFSET, localFileHeaderOffset);
         return new CentralDirectoryRecord(
                 result,
+                mGpFlags,
+                mCompressionMethod,
                 mLastModificationTime,
                 mLastModificationDate,
                 mCrc32,
@@ -204,14 +224,16 @@
             long uncompressedSize,
             long localFileHeaderOffset) {
         byte[] nameBytes = name.getBytes(StandardCharsets.UTF_8);
+        short gpFlags = ZipUtils.GP_FLAG_EFS; // UTF-8 character encoding used for entry name
+        short compressionMethod = ZipUtils.COMPRESSION_METHOD_DEFLATED;
         int recordSize = HEADER_SIZE_BYTES + nameBytes.length;
         ByteBuffer result = ByteBuffer.allocate(recordSize);
         result.order(ByteOrder.LITTLE_ENDIAN);
         result.putInt(RECORD_SIGNATURE);
         ZipUtils.putUnsignedInt16(result, 0x14); // Version made by
         ZipUtils.putUnsignedInt16(result, 0x14); // Minimum version needed to extract
-        result.putShort(ZipUtils.GP_FLAG_EFS); // UTF-8 character encoding used for entry name
-        result.putShort(ZipUtils.COMPRESSION_METHOD_DEFLATED);
+        result.putShort(gpFlags);
+        result.putShort(compressionMethod);
         ZipUtils.putUnsignedInt16(result, lastModifiedTime);
         ZipUtils.putUnsignedInt16(result, lastModifiedDate);
         ZipUtils.putUnsignedInt32(result, crc32);
@@ -232,6 +254,8 @@
         result.flip();
         return new CentralDirectoryRecord(
                 result,
+                gpFlags,
+                compressionMethod,
                 lastModifiedTime,
                 lastModifiedDate,
                 crc32,
diff --git a/src/main/java/com/android/apksig/internal/zip/LocalFileRecord.java b/src/main/java/com/android/apksig/internal/zip/LocalFileRecord.java
index f2f4d0b..0a55b1a 100644
--- a/src/main/java/com/android/apksig/internal/zip/LocalFileRecord.java
+++ b/src/main/java/com/android/apksig/internal/zip/LocalFileRecord.java
@@ -38,7 +38,6 @@
     private static final int HEADER_SIZE_BYTES = 30;
 
     private static final int GP_FLAGS_OFFSET = 6;
-    private static final int COMPRESSION_METHOD_OFFSET = 8;
     private static final int CRC32_OFFSET = 14;
     private static final int COMPRESSED_SIZE_OFFSET = 18;
     private static final int UNCOMPRESSED_SIZE_OFFSET = 22;
@@ -175,6 +174,14 @@
         }
         short gpFlags = header.getShort(GP_FLAGS_OFFSET);
         boolean dataDescriptorUsed = (gpFlags & ZipUtils.GP_FLAG_DATA_DESCRIPTOR_USED) != 0;
+        boolean cdDataDescriptorUsed =
+                (cdRecord.getGpFlags() & ZipUtils.GP_FLAG_DATA_DESCRIPTOR_USED) != 0;
+        if (dataDescriptorUsed != cdDataDescriptorUsed) {
+            throw new ZipFormatException(
+                    "Data Descriptor presence mismatch between Local File Header and Central"
+                            + " Directory for entry " + entryName
+                            + ". LFH: " + dataDescriptorUsed + ", CD: " + cdDataDescriptorUsed);
+        }
         long uncompressedDataCrc32FromCdRecord = cdRecord.getCrc32();
         long compressedDataSizeFromCdRecord = cdRecord.getCompressedSize();
         long uncompressedDataSizeFromCdRecord = cdRecord.getUncompressedSize();
@@ -215,24 +222,10 @@
                             + name + "\", CD: \"" + entryName + "\"");
         }
         int extraLength = ZipUtils.getUnsignedInt16(header, EXTRA_LENGTH_OFFSET);
-
-        short compressionMethod = header.getShort(COMPRESSION_METHOD_OFFSET);
-        boolean compressed;
-        switch (compressionMethod) {
-            case ZipUtils.COMPRESSION_METHOD_STORED:
-                compressed = false;
-                break;
-            case ZipUtils.COMPRESSION_METHOD_DEFLATED:
-                compressed = true;
-                break;
-            default:
-                throw new ZipFormatException(
-                        "Unsupported compression method of entry " + entryName
-                                + ": " + (compressionMethod & 0xffff));
-        }
-
         long dataStartOffset = headerStartOffset + HEADER_SIZE_BYTES + nameLength + extraLength;
         long dataSize;
+        boolean compressed =
+                (cdRecord.getCompressionMethod() != ZipUtils.COMPRESSION_METHOD_STORED);
         if (compressed) {
             dataSize = compressedDataSizeFromCdRecord;
         } else {
diff --git a/src/main/java/com/android/apksig/util/DataSink.java b/src/main/java/com/android/apksig/util/DataSink.java
index e2d06ee..5042933 100644
--- a/src/main/java/com/android/apksig/util/DataSink.java
+++ b/src/main/java/com/android/apksig/util/DataSink.java
@@ -29,6 +29,9 @@
      *
      * <p>This data sink guarantees to not hold references to the provided buffer after this method
      * terminates.
+     *
+     * @throws IndexOutOfBoundsException if {@code offset} or {@code length} are negative, or if
+     *         {@code offset + length} is greater than {@code buf.length}.
      */
     void consume(byte[] buf, int offset, int length) throws IOException;
 
diff --git a/src/main/java/com/android/apksig/util/DataSource.java b/src/main/java/com/android/apksig/util/DataSource.java
index 86014ca..a89a87c 100644
--- a/src/main/java/com/android/apksig/util/DataSource.java
+++ b/src/main/java/com/android/apksig/util/DataSource.java
@@ -62,6 +62,9 @@
      *
      * @param offset index (in bytes) at which the chunk starts inside data source
      * @param size size (in bytes) of the chunk
+     *
+     * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative, or if
+     *         {@code offset + size} is greater than {@link #size()}.
      */
     void feed(long offset, long size, DataSink sink) throws IOException;
 
@@ -75,6 +78,9 @@
      *
      * @param offset index (in bytes) at which the chunk starts inside data source
      * @param size size (in bytes) of the chunk
+     *
+     * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative, or if
+     *         {@code offset + size} is greater than {@link #size()}.
      */
     ByteBuffer getByteBuffer(long offset, int size) throws IOException;
 
@@ -84,6 +90,9 @@
      *
      * @param offset index (in bytes) at which the chunk starts inside data source
      * @param size size (in bytes) of the chunk
+     *
+     * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative, or if
+     *         {@code offset + size} is greater than {@link #size()}.
      */
     void copyTo(long offset, int size, ByteBuffer dest) throws IOException;
 
@@ -93,6 +102,9 @@
      *
      * @param offset index (in bytes) at which the region starts inside data source
      * @param size size (in bytes) of the region
+     *
+     * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative, or if
+     *         {@code offset + size} is greater than {@link #size()}.
      */
     DataSource slice(long offset, long size);
 }
diff --git a/src/test/java/com/android/apksig/AllTests.java b/src/test/java/com/android/apksig/AllTests.java
new file mode 100644
index 0000000..cd5c909
--- /dev/null
+++ b/src/test/java/com/android/apksig/AllTests.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+    ApkSignerTest.class,
+    ApkVerifierTest.class,
+    com.android.apksig.apk.AllTests.class,
+    com.android.apksig.internal.AllTests.class,
+    com.android.apksig.util.AllTests.class,
+})
+public class AllTests {}
diff --git a/src/test/java/com/android/apksig/ApkSignerTest.java b/src/test/java/com/android/apksig/ApkSignerTest.java
new file mode 100644
index 0000000..513023e
--- /dev/null
+++ b/src/test/java/com/android/apksig/ApkSignerTest.java
@@ -0,0 +1,459 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig;
+
+import static org.junit.Assert.fail;
+
+import com.android.apksig.ApkVerifier.Issue;
+import com.android.apksig.apk.ApkFormatException;
+import com.android.apksig.internal.util.Resources;
+import com.android.apksig.util.DataSinks;
+import com.android.apksig.util.DataSource;
+import com.android.apksig.util.DataSources;
+import com.android.apksig.util.ReadableDataSink;
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ByteChannel;
+import java.nio.file.Files;
+import java.nio.file.StandardOpenOption;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ApkSignerTest {
+
+    /**
+     * Whether to preserve, as files, outputs of failed tests. This is useful for investigating test
+     * failures.
+     */
+    private static final boolean KEEP_FAILING_OUTPUT_AS_FILES = false;
+
+    public static void main(String[] params) throws Exception {
+        File outDir = (params.length > 0) ? new File(params[0]) : new File(".");
+        generateGoldenFiles(outDir);
+    }
+
+    private static void generateGoldenFiles(File outDir) throws Exception {
+        System.out.println(
+                "Generating golden files " + ApkSignerTest.class.getSimpleName()
+                    + " into " + outDir);
+        if (!outDir.mkdirs()) {
+            throw new IOException("Failed to create directory: " + outDir);
+        }
+        List<ApkSigner.SignerConfig> rsa2048SignerConfig =
+                Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048"));
+
+        signGolden(
+                "golden-unaligned-in.apk",
+                new File(outDir, "golden-unaligned-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig));
+        signGolden(
+                "golden-legacy-aligned-in.apk",
+                new File(outDir, "golden-legacy-aligned-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig));
+        signGolden(
+                "golden-aligned-in.apk",
+                new File(outDir, "golden-aligned-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig));
+
+        signGolden(
+                "golden-unaligned-in.apk",
+                new File(outDir, "golden-unaligned-v1-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(false));
+        signGolden(
+                "golden-legacy-aligned-in.apk",
+                new File(outDir, "golden-legacy-aligned-v1-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(false));
+        signGolden(
+                "golden-aligned-in.apk",
+                new File(outDir, "golden-aligned-v1-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(false));
+
+        signGolden(
+                "golden-unaligned-in.apk",
+                new File(outDir, "golden-unaligned-v2-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(true));
+        signGolden(
+                "golden-legacy-aligned-in.apk",
+                new File(outDir, "golden-legacy-aligned-v2-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(true));
+        signGolden(
+                "golden-aligned-in.apk",
+                new File(outDir, "golden-aligned-v2-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(true));
+
+        signGolden(
+                "golden-unaligned-in.apk",
+                new File(outDir, "golden-unaligned-v1v2-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(true));
+        signGolden(
+                "golden-legacy-aligned-in.apk",
+                new File(outDir, "golden-legacy-aligned-v1v2-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(true));
+        signGolden(
+                "golden-aligned-in.apk",
+                new File(outDir, "golden-aligned-v1v2-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(true));
+
+
+        signGolden(
+                "original.apk", new File(outDir, "golden-rsa-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig));
+        signGolden(
+                "original.apk", new File(outDir, "golden-rsa-minSdkVersion-1-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig).setMinSdkVersion(1));
+        signGolden(
+                "original.apk", new File(outDir, "golden-rsa-minSdkVersion-18-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig).setMinSdkVersion(18));
+        signGolden(
+                "original.apk", new File(outDir, "golden-rsa-minSdkVersion-24-out.apk"),
+                new ApkSigner.Builder(rsa2048SignerConfig).setMinSdkVersion(24));
+    }
+
+    private static void signGolden(
+            String inResourceName, File outFile, ApkSigner.Builder apkSignerBuilder)
+                    throws Exception {
+        DataSource in =
+                DataSources.asDataSource(
+                        ByteBuffer.wrap(Resources.toByteArray(ApkSigner.class, inResourceName)));
+        apkSignerBuilder
+                .setInputApk(in)
+                .setOutputApk(outFile)
+                .build()
+                .sign();
+    }
+
+    @Test
+    public void testAlignmentPreserved_Golden() throws Exception {
+        // Regression tests for preserving (mis)alignment of ZIP Local File Header data
+        // NOTE: Expected output files can be re-generated by running the "main" method.
+
+        List<ApkSigner.SignerConfig> rsa2048SignerConfig =
+                Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048"));
+
+        // 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
+        // 4 kB boundary, but the data isn't actually aligned in the file.
+        assertGolden(
+                "golden-unaligned-in.apk", "golden-unaligned-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfig));
+        assertGolden(
+                "golden-unaligned-in.apk", "golden-unaligned-v1-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(false));
+        assertGolden(
+                "golden-unaligned-in.apk", "golden-unaligned-v2-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(true));
+        assertGolden(
+                "golden-unaligned-in.apk", "golden-unaligned-v1v2-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(true));
+
+        // 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
+        // archives whose "extra" field are not compliant with APPNOTE.TXT. Hence, this technique
+        // was deprecated.
+        assertGolden(
+                "golden-legacy-aligned-in.apk", "golden-legacy-aligned-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfig));
+        assertGolden(
+                "golden-legacy-aligned-in.apk", "golden-legacy-aligned-v1-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(false));
+        assertGolden(
+                "golden-legacy-aligned-in.apk", "golden-legacy-aligned-v2-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(true));
+        assertGolden(
+                "golden-legacy-aligned-in.apk", "golden-legacy-aligned-v1v2-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(true));
+
+        // 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
+        // are compliant with APPNOTE.TXT.
+        assertGolden(
+                "golden-aligned-in.apk", "golden-aligned-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfig));
+        assertGolden(
+                "golden-aligned-in.apk", "golden-aligned-v1-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(false));
+        assertGolden(
+                "golden-aligned-in.apk", "golden-aligned-v2-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(false)
+                        .setV2SigningEnabled(true));
+        assertGolden(
+                "golden-aligned-in.apk", "golden-aligned-v1v2-out.apk",
+                new ApkSigner.Builder(rsa2048SignerConfig)
+                        .setV1SigningEnabled(true)
+                        .setV2SigningEnabled(true));
+    }
+
+    @Test
+    public void testMinSdkVersion_Golden() throws Exception {
+        // 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"));
+        assertGolden("original.apk", "golden-rsa-out.apk", new ApkSigner.Builder(rsaSignerConfig));
+        assertGolden(
+                "original.apk", "golden-rsa-minSdkVersion-1-out.apk",
+                new ApkSigner.Builder(rsaSignerConfig).setMinSdkVersion(1));
+        assertGolden(
+                "original.apk", "golden-rsa-minSdkVersion-18-out.apk",
+                new ApkSigner.Builder(rsaSignerConfig).setMinSdkVersion(18));
+        assertGolden(
+                "original.apk", "golden-rsa-minSdkVersion-24-out.apk",
+                new ApkSigner.Builder(rsaSignerConfig).setMinSdkVersion(24));
+
+        // TODO: Add tests for DSA and ECDSA. This is non-trivial because the default
+        // implementations of these signature algorithms are non-deterministic which means output
+        // files always differ from golden files.
+    }
+
+    @Test
+    public void testRsaSignedVerifies() throws Exception {
+        List<ApkSigner.SignerConfig> signers =
+                Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048"));
+        String in = "original.apk";
+
+        // Sign so that the APK is guaranteed to verify on API Level 1+
+        DataSource out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(1));
+        assertVerified(verifyForMinSdkVersion(out, 1));
+
+        // Sign so that the APK is guaranteed to verify on API Level 18+
+        out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(18));
+        assertVerified(verifyForMinSdkVersion(out, 18));
+        // Does not verify on API Level 17 because RSA with SHA-256 not supported
+        assertVerificationFailure(
+                verifyForMinSdkVersion(out, 17), Issue.JAR_SIG_UNSUPPORTED_SIG_ALG);
+    }
+
+    @Test
+    public void testDsaSignedVerifies() throws Exception {
+        List<ApkSigner.SignerConfig> signers =
+                Collections.singletonList(getDefaultSignerConfigFromResources("dsa-1024"));
+        String in = "original.apk";
+
+        // Sign so that the APK is guaranteed to verify on API Level 1+
+        DataSource out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(1));
+        assertVerified(verifyForMinSdkVersion(out, 1));
+
+        // Sign so that the APK is guaranteed to verify on API Level 21+
+        out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(21));
+        assertVerified(verifyForMinSdkVersion(out, 21));
+        // Does not verify on API Level 20 because DSA with SHA-256 not supported
+        assertVerificationFailure(
+                verifyForMinSdkVersion(out, 20), Issue.JAR_SIG_UNSUPPORTED_SIG_ALG);
+    }
+
+    @Test
+    public void testEcSignedVerifies() throws Exception {
+        List<ApkSigner.SignerConfig> signers =
+                Collections.singletonList(getDefaultSignerConfigFromResources("ec-p256"));
+        String in = "original.apk";
+
+        // NOTE: EC APK signatures are not supported prior to API Level 18
+        // Sign so that the APK is guaranteed to verify on API Level 18+
+        DataSource out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(18));
+        assertVerified(verifyForMinSdkVersion(out, 18));
+        // Does not verify on API Level 17 because EC not supported
+        assertVerificationFailure(
+                verifyForMinSdkVersion(out, 17), Issue.JAR_SIG_UNSUPPORTED_SIG_ALG);
+    }
+
+    @Test
+    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"));
+        try {
+            sign("v1-only-with-cr-in-entry-name.apk",
+                    new ApkSigner.Builder(signers).setV1SigningEnabled(true));
+            fail();
+        } catch (ApkFormatException expected) {}
+
+        try {
+            sign("v1-only-with-lf-in-entry-name.apk",
+                    new ApkSigner.Builder(signers).setV1SigningEnabled(true));
+            fail();
+        } catch (ApkFormatException expected) {}
+
+        try {
+            sign("v1-only-with-nul-in-entry-name.apk",
+                    new ApkSigner.Builder(signers).setV1SigningEnabled(true));
+            fail();
+        } catch (ApkFormatException expected) {}
+    }
+
+    @Test
+    public void testWeirdZipCompressionMethod() throws Exception {
+        // 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"));
+        sign("weird-compression-method.apk", new ApkSigner.Builder(signers));
+    }
+
+    @Test
+    public void testZipCompressionMethodMismatchBetweenLfhAndCd() throws Exception {
+        // Android Package Manager ignores compressionMethod field in Local File Header and always
+        // 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"));
+        sign("mismatched-compression-method.apk", new ApkSigner.Builder(signers));
+    }
+
+    /**
+     * Asserts that signing the specified golden input file using the provided signing
+     * configuration produces output identical to the specified golden output file.
+     */
+    private void assertGolden(
+            String inResourceName, String expectedOutResourceName,
+            ApkSigner.Builder apkSignerBuilder) throws Exception {
+        // Sign the provided golden input
+        DataSource out = sign(inResourceName, apkSignerBuilder);
+
+        // Assert that the output is identical to the provided golden output
+        if (out.size() > Integer.MAX_VALUE) {
+            throw new RuntimeException("Output too large: " + out.size() + " bytes");
+        }
+        ByteBuffer actualOutBuf = out.getByteBuffer(0, (int) out.size());
+
+        ByteBuffer expectedOutBuf =
+                ByteBuffer.wrap(Resources.toByteArray(getClass(), expectedOutResourceName));
+
+        int actualStartPos = actualOutBuf.position();
+        boolean identical = false;
+        if (actualOutBuf.remaining() == expectedOutBuf.remaining()) {
+            while (actualOutBuf.hasRemaining()) {
+                if (actualOutBuf.get() != expectedOutBuf.get()) {
+                    break;
+                }
+            }
+            identical = !actualOutBuf.hasRemaining();
+        }
+
+        if (identical) {
+            return;
+        }
+        actualOutBuf.position(actualStartPos);
+
+        if (KEEP_FAILING_OUTPUT_AS_FILES) {
+            File tmp = File.createTempFile(getClass().getSimpleName(), ".apk");
+            try (ByteChannel outChannel =
+                    Files.newByteChannel(
+                            tmp.toPath(),
+                            StandardOpenOption.WRITE,
+                            StandardOpenOption.CREATE,
+                            StandardOpenOption.TRUNCATE_EXISTING)) {
+                while (actualOutBuf.hasRemaining()) {
+                    outChannel.write(actualOutBuf);
+                }
+            }
+            fail(tmp + " differs from " + expectedOutResourceName);
+        } else {
+            fail("Output differs from " + expectedOutResourceName);
+        }
+    }
+
+    private DataSource sign(
+            String inResourceName, ApkSigner.Builder apkSignerBuilder) throws Exception {
+        DataSource in =
+                DataSources.asDataSource(
+                        ByteBuffer.wrap(Resources.toByteArray(getClass(), inResourceName)));
+        ReadableDataSink out = DataSinks.newInMemoryDataSink();
+        apkSignerBuilder
+                .setInputApk(in)
+                .setOutputApk(out)
+                .build()
+                .sign();
+        return out;
+    }
+
+    private static ApkVerifier.Result verifyForMinSdkVersion(DataSource apk, int minSdkVersion)
+            throws IOException, ApkFormatException, NoSuchAlgorithmException {
+        return verify(apk, minSdkVersion);
+    }
+
+    private static ApkVerifier.Result verify(DataSource apk, Integer minSdkVersionOverride)
+            throws IOException, ApkFormatException, NoSuchAlgorithmException {
+        ApkVerifier.Builder builder = new ApkVerifier.Builder(apk);
+        if (minSdkVersionOverride != null) {
+            builder.setMinCheckedPlatformVersion(minSdkVersionOverride);
+        }
+        return builder.build().verify();
+    }
+
+    private static void assertVerified(ApkVerifier.Result result) {
+        ApkVerifierTest.assertVerified(result);
+    }
+
+    private static void assertVerificationFailure(ApkVerifier.Result result, Issue expectedIssue) {
+        ApkVerifierTest.assertVerificationFailure(result, expectedIssue);
+    }
+
+    private static ApkSigner.SignerConfig getDefaultSignerConfigFromResources(
+            String keyNameInResources) throws Exception {
+        PrivateKey privateKey =
+                Resources.toPrivateKey(ApkSignerTest.class, keyNameInResources + ".pk8");
+        List<X509Certificate> certs =
+                Resources.toCertificateChain(ApkSignerTest.class, keyNameInResources + ".x509.pem");
+        return new ApkSigner.SignerConfig.Builder(keyNameInResources, privateKey, certs).build();
+    }
+}
diff --git a/src/test/java/com/android/apksig/ApkVerifierTest.java b/src/test/java/com/android/apksig/ApkVerifierTest.java
new file mode 100644
index 0000000..2bb3487
--- /dev/null
+++ b/src/test/java/com/android/apksig/ApkVerifierTest.java
@@ -0,0 +1,890 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeNoException;
+
+import com.android.apksig.ApkVerifier.Issue;
+import com.android.apksig.ApkVerifier.IssueWithParams;
+import com.android.apksig.apk.ApkFormatException;
+import com.android.apksig.internal.util.AndroidSdkVersion;
+import com.android.apksig.internal.util.HexEncoding;
+import com.android.apksig.internal.util.Resources;
+import com.android.apksig.util.DataSources;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.Signature;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.List;
+import java.util.Locale;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ApkVerifierTest {
+
+    private static final String[] DSA_KEY_NAMES = {"1024", "2048", "3072"};
+    private static final String[] DSA_KEY_NAMES_1024_AND_SMALLER = {"1024"};
+    private static final String[] DSA_KEY_NAMES_2048_AND_LARGER = {"2048", "3072"};
+    private static final String[] EC_KEY_NAMES = {"p256", "p384", "p521"};
+    private static final String[] RSA_KEY_NAMES = {"1024", "2048", "3072", "4096", "8192", "16384"};
+    private static final String[] RSA_KEY_NAMES_2048_AND_LARGER =
+            {"2048", "3072", "4096", "8192", "16384"};
+
+    @Test
+    public void testOriginalAccepted() throws Exception {
+        // APK signed with v1 and v2 schemes. Obtained by building
+        // cts/hostsidetests/appsecurity/test-apps/tinyapp.
+        // This APK is used as a basis for many of the other tests here. Hence, we check that this
+        // APK verifies.
+        assertVerified(verify("original.apk"));
+    }
+
+    @Test
+    public void testV1OneSignerMD5withRSAAccepted() throws Exception {
+        assumeThatMd5AcceptedInPkcs7Signature();
+
+        // APK signed with v1 scheme only, one signer
+        assertVerifiedForEach(
+                "v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES);
+        assertVerifiedForEach(
+                "v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-%s.apk", RSA_KEY_NAMES);
+    }
+
+    @Test
+    public void testV1OneSignerSHA1withRSAAccepted() throws Exception {
+        // APK signed with v1 scheme only, one signer
+        assertVerifiedForEach(
+                "v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES);
+        assertVerifiedForEach(
+                "v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-%s.apk", RSA_KEY_NAMES);
+    }
+
+    @Test
+    public void testV1OneSignerSHA224withRSAAccepted() throws Exception {
+        // APK signed with v1 scheme only, one signer
+        assertVerifiedForEach(
+                "v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES);
+        assertVerifiedForEach(
+                "v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-%s.apk", RSA_KEY_NAMES);
+    }
+
+    @Test
+    public void testV1OneSignerSHA256withRSAAccepted() throws Exception {
+        // APK signed with v1 scheme only, one signer
+        assertVerifiedForEach(
+                "v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES);
+        assertVerifiedForEach(
+                "v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-%s.apk", RSA_KEY_NAMES);
+    }
+
+    @Test
+    public void testV1OneSignerSHA384withRSAAccepted() throws Exception {
+        // APK signed with v1 scheme only, one signer
+        assertVerifiedForEach(
+                "v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES);
+        assertVerifiedForEach(
+                "v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-%s.apk", RSA_KEY_NAMES);
+    }
+
+    @Test
+    public void testV1OneSignerSHA512withRSAVerifies() throws Exception {
+        // APK signed with v1 scheme only, one signer
+        assertVerifiedForEach(
+                "v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES);
+        assertVerifiedForEach(
+                "v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-%s.apk", RSA_KEY_NAMES);
+    }
+
+    @Test
+    public void testV1OneSignerSHA1withECDSAAccepted() throws Exception {
+        // APK signed with v1 scheme only, one signer
+        assertVerifiedForEach(
+                "v1-only-with-ecdsa-sha1-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES);
+        assertVerifiedForEach(
+                "v1-only-with-ecdsa-sha1-1.2.840.10045.4.1-%s.apk", EC_KEY_NAMES);
+    }
+
+    @Test
+    public void testV1OneSignerSHA224withECDSAAccepted() throws Exception {
+        // APK signed with v1 scheme only, one signer
+        assertVerifiedForEach(
+                "v1-only-with-ecdsa-sha224-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES);
+        assertVerifiedForEach(
+                "v1-only-with-ecdsa-sha224-1.2.840.10045.4.3.1-%s.apk", EC_KEY_NAMES);
+    }
+
+    @Test
+    public void testV1OneSignerSHA256withECDSAAccepted() throws Exception {
+        // APK signed with v1 scheme only, one signer
+        assertVerifiedForEach(
+                "v1-only-with-ecdsa-sha256-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES);
+        assertVerifiedForEach(
+                "v1-only-with-ecdsa-sha256-1.2.840.10045.4.3.2-%s.apk", EC_KEY_NAMES);
+    }
+
+    @Test
+    public void testV1OneSignerSHA384withECDSAAccepted() throws Exception {
+        // APK signed with v1 scheme only, one signer
+        assertVerifiedForEach(
+                "v1-only-with-ecdsa-sha384-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES);
+        assertVerifiedForEach(
+                "v1-only-with-ecdsa-sha384-1.2.840.10045.4.3.3-%s.apk", EC_KEY_NAMES);
+    }
+
+    @Test
+    public void testV1OneSignerSHA512withECDSAAccepted() throws Exception {
+        // APK signed with v1 scheme only, one signer
+        assertVerifiedForEach(
+                "v1-only-with-ecdsa-sha512-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES);
+        assertVerifiedForEach(
+                "v1-only-with-ecdsa-sha512-1.2.840.10045.4.3.4-%s.apk", EC_KEY_NAMES);
+    }
+
+    @Test
+    public void testV1OneSignerSHA1withDSAAccepted() throws Exception {
+        // APK signed with v1 scheme only, one signer
+        // NOTE: This test is split into two because JCA Providers shipping with OpenJDK refuse to
+        // verify DSA signatures with keys too long for the SHA-1 digest.
+        assertVerifiedForEach(
+                "v1-only-with-dsa-sha1-1.2.840.10040.4.1-%s.apk", DSA_KEY_NAMES_1024_AND_SMALLER);
+        assertVerifiedForEach(
+                "v1-only-with-dsa-sha1-1.2.840.10040.4.3-%s.apk", DSA_KEY_NAMES_1024_AND_SMALLER);
+    }
+
+    @Test
+    public void testV1OneSignerSHA1withDSAAcceptedWithKeysTooLongForDigest() throws Exception {
+        // APK signed with v1 scheme only, one signer
+
+        // OpenJDK's default implementation of Signature.SHA1withDSA refuses to verify signatures
+        // created with keys too long for the digest used. Android Package Manager does not reject
+        // such signatures. We thus skip this test if Signature.SHA1withDSA exhibits this issue.
+        PublicKey publicKey =
+                Resources.toCertificate(getClass(), "dsa-2048.x509.pem").getPublicKey();
+        Signature s = Signature.getInstance("SHA1withDSA");
+        try {
+            s.initVerify(publicKey);
+        } catch (InvalidKeyException e) {
+            assumeNoException(e);
+        }
+
+        assertVerifiedForEach(
+                "v1-only-with-dsa-sha1-1.2.840.10040.4.1-%s.apk", DSA_KEY_NAMES_2048_AND_LARGER);
+        assertVerifiedForEach(
+                "v1-only-with-dsa-sha1-1.2.840.10040.4.3-%s.apk", DSA_KEY_NAMES_2048_AND_LARGER);
+    }
+
+    @Test
+    public void testV1OneSignerSHA224withDSAAccepted() throws Exception {
+        // APK signed with v1 scheme only, one signer
+        // NOTE: This test is split into two because JCA Providers shipping with OpenJDK refuse to
+        // verify DSA signatures with keys too long for the SHA-224 digest.
+        assertVerifiedForEach(
+                "v1-only-with-dsa-sha224-1.2.840.10040.4.1-%s.apk", DSA_KEY_NAMES_1024_AND_SMALLER);
+        assertVerifiedForEach(
+                "v1-only-with-dsa-sha224-2.16.840.1.101.3.4.3.1-%s.apk",
+                DSA_KEY_NAMES_1024_AND_SMALLER);
+    }
+
+    @Test
+    public void testV1OneSignerSHA224withDSAAcceptedWithKeysTooLongForDigest() throws Exception {
+        // APK signed with v1 scheme only, one signer
+
+        // OpenJDK's default implementation of Signature.SHA224withDSA refuses to verify signatures
+        // created with keys too long for the digest used. Android Package Manager does not reject
+        // such signatures. We thus skip this test if Signature.SHA224withDSA exhibits this issue.
+        PublicKey publicKey =
+                Resources.toCertificate(getClass(), "dsa-2048.x509.pem").getPublicKey();
+        Signature s = Signature.getInstance("SHA224withDSA");
+        try {
+            s.initVerify(publicKey);
+        } catch (InvalidKeyException e) {
+            assumeNoException(e);
+        }
+        assertVerifiedForEach(
+                "v1-only-with-dsa-sha224-1.2.840.10040.4.1-%s.apk", DSA_KEY_NAMES_2048_AND_LARGER);
+        assertVerifiedForEach(
+                "v1-only-with-dsa-sha224-2.16.840.1.101.3.4.3.1-%s.apk",
+                DSA_KEY_NAMES_2048_AND_LARGER);
+    }
+
+    @Test
+    public void testV1OneSignerSHA256withDSAAccepted() throws Exception {
+        // APK signed with v1 scheme only, one signer
+        assertVerifiedForEach(
+                "v1-only-with-dsa-sha256-1.2.840.10040.4.1-%s.apk", DSA_KEY_NAMES);
+        assertVerifiedForEach(
+                "v1-only-with-dsa-sha256-2.16.840.1.101.3.4.3.2-%s.apk", DSA_KEY_NAMES);
+    }
+
+    @Test
+    public void testV2StrippedRejected() throws Exception {
+        // APK signed with v1 and v2 schemes, but v2 signature was stripped from the file (by using
+        // zipalign).
+        // This should fail because the v1 signature indicates that the APK was supposed to be
+        // signed with v2 scheme as well, making the platform's anti-stripping protections reject
+        // the APK.
+        assertVerificationFailure(
+                "v2-stripped.apk", Issue.JAR_SIG_MISSING_APK_SIG_REFERENCED);
+
+        // Similar to above, but the X-Android-APK-Signed anti-stripping header in v1 signature
+        // lists unknown signature schemes in addition to APK Signature Scheme v2. Unknown schemes
+        // should be ignored.
+        assertVerificationFailure(
+                "v2-stripped-with-ignorable-signing-schemes.apk",
+                Issue.JAR_SIG_MISSING_APK_SIG_REFERENCED);
+    }
+
+    @Test
+    public void testV2OneSignerOneSignatureAccepted() throws Exception {
+        // APK signed with v2 scheme only, one signer, one signature
+        assertVerifiedForEachForMinSdkVersion(
+                "v2-only-with-dsa-sha256-%s.apk", DSA_KEY_NAMES, AndroidSdkVersion.N);
+        assertVerifiedForEachForMinSdkVersion(
+                "v2-only-with-ecdsa-sha256-%s.apk", EC_KEY_NAMES, AndroidSdkVersion.N);
+        assertVerifiedForEachForMinSdkVersion(
+                "v2-only-with-rsa-pkcs1-sha256-%s.apk", RSA_KEY_NAMES, AndroidSdkVersion.N);
+        // RSA-PSS signatures tested in a separate test below
+
+        // DSA with SHA-512 is not supported by Android platform and thus APK Signature Scheme v2
+        // does not support that either
+        // assertInstallSucceedsForEach("v2-only-with-dsa-sha512-%s.apk", DSA_KEY_NAMES);
+        assertVerifiedForEachForMinSdkVersion(
+                "v2-only-with-ecdsa-sha512-%s.apk", EC_KEY_NAMES, AndroidSdkVersion.N);
+        assertVerifiedForEachForMinSdkVersion(
+                "v2-only-with-rsa-pkcs1-sha512-%s.apk", RSA_KEY_NAMES, AndroidSdkVersion.N);
+    }
+
+    @Test
+    public void testV2OneSignerOneRsaPssSignatureAccepted() throws Exception {
+        assumeThatRsaPssAvailable();
+        // APK signed with v2 scheme only, one signer, one signature
+        assertVerifiedForEachForMinSdkVersion(
+                "v2-only-with-rsa-pss-sha256-%s.apk", RSA_KEY_NAMES, AndroidSdkVersion.N);
+        assertVerifiedForEachForMinSdkVersion(
+                "v2-only-with-rsa-pss-sha512-%s.apk",
+                RSA_KEY_NAMES_2048_AND_LARGER, // 1024-bit key is too short for PSS with SHA-512
+                AndroidSdkVersion.N);
+    }
+
+    @Test
+    public void testV2SignatureDoesNotMatchSignedDataRejected() throws Exception {
+        // APK signed with v2 scheme only, but the signature over signed-data does not verify
+
+        // Bitflip in certificate field inside signed-data. Based on
+        // v2-only-with-dsa-sha256-1024.apk.
+        assertVerificationFailure(
+                "v2-only-with-dsa-sha256-1024-sig-does-not-verify.apk",
+                Issue.V2_SIG_DID_NOT_VERIFY);
+
+        // Signature claims to be RSA PKCS#1 v1.5 with SHA-256, but is actually using SHA-512.
+        // Based on v2-only-with-rsa-pkcs1-sha256-2048.apk.
+        assertVerificationFailure(
+                "v2-only-with-rsa-pkcs1-sha256-2048-sig-does-not-verify.apk",
+                Issue.V2_SIG_VERIFY_EXCEPTION);
+
+        // Bitflip in the ECDSA signature. Based on v2-only-with-ecdsa-sha256-p256.apk.
+        assertVerificationFailure(
+                "v2-only-with-ecdsa-sha256-p256-sig-does-not-verify.apk",
+                Issue.V2_SIG_DID_NOT_VERIFY);
+    }
+
+    @Test
+    public void testV2RsaPssSignatureDoesNotMatchSignedDataRejected() throws Exception {
+        assumeThatRsaPssAvailable();
+
+        // APK signed with v2 scheme only, but the signature over signed-data does not verify.
+
+        // Signature claims to be RSA PSS with SHA-256 and 32 bytes of salt, but is actually using 0
+        // bytes of salt. Based on v2-only-with-rsa-pkcs1-sha256-2048.apk. Obtained by modifying APK
+        // signer to use the wrong amount of salt.
+        assertVerificationFailure(
+                "v2-only-with-rsa-pss-sha256-2048-sig-does-not-verify.apk",
+                Issue.V2_SIG_DID_NOT_VERIFY);
+    }
+
+    @Test
+    public void testV2ContentDigestMismatchRejected() throws Exception {
+        // APK signed with v2 scheme only, but the digest of contents does not match the digest
+        // stored in signed-data
+        ApkVerifier.Issue error = Issue.V2_SIG_APK_DIGEST_DID_NOT_VERIFY;
+
+        // Based on v2-only-with-rsa-pkcs1-sha512-4096.apk. Obtained by modifying APK signer to
+        // flip the leftmost bit in content digest before signing signed-data.
+        assertVerificationFailure(
+                "v2-only-with-rsa-pkcs1-sha512-4096-digest-mismatch.apk", error);
+
+        // Based on v2-only-with-ecdsa-sha256-p256.apk. Obtained by modifying APK signer to flip the
+        // leftmost bit in content digest before signing signed-data.
+        assertVerificationFailure(
+                "v2-only-with-ecdsa-sha256-p256-digest-mismatch.apk", error);
+    }
+
+    @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.
+
+        // Obtained from v2-only-with-rsa-pkcs1-sha512-4096.apk by flipping a bit in the magic
+        // field in the footer of APK Signing Block. This makes the APK Signing Block disappear.
+        assertVerificationFailure(
+                "v2-only-wrong-apk-sig-block-magic.apk",
+                Issue.JAR_SIG_NO_MANIFEST);
+
+        // Obtained by modifying APK signer to insert "GARBAGE" between ZIP Central Directory and
+        // End of Central Directory. The APK is otherwise fine and is signed with APK Signature
+        // Scheme v2. Based on v2-only-with-rsa-pkcs1-sha256.apk.
+        assertVerificationFailure(
+                "v2-only-garbage-between-cd-and-eocd.apk",
+                Issue.JAR_SIG_NO_MANIFEST);
+
+        // Obtained by modifying the size in APK Signature Block header. Based on
+        // v2-only-with-ecdsa-sha512-p521.apk.
+        assertVerificationFailure(
+                "v2-only-apk-sig-block-size-mismatch.apk",
+                Issue.JAR_SIG_NO_MANIFEST);
+
+        // Obtained by modifying the ID under which APK Signature Scheme v2 Block is stored in
+        // APK Signing Block and by modifying the APK signer to not insert anti-stripping
+        // protections into JAR Signature. The APK should appear as having no APK Signature Scheme
+        // v2 Block and should thus successfully verify using JAR Signature Scheme.
+        assertVerified(verify("v1-with-apk-sig-block-but-without-apk-sig-scheme-v2-block.apk"));
+    }
+
+    @Test(expected = ApkFormatException.class)
+    public void testTruncatedZipCentralDirectoryRejected() throws Exception {
+        // Obtained by modifying APK signer to truncate the ZIP Central Directory by one byte. The
+        // APK is otherwise fine and is signed with APK Signature Scheme v2. Based on
+        // v2-only-with-rsa-pkcs1-sha256.apk
+        verify("v2-only-truncated-cd.apk");
+    }
+
+    @Test
+    public void testV2UnknownPairIgnoredInApkSigningBlock() 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 v2 Block. The unknown
+        // ID-value should be ignored.
+        assertVerified(
+                verifyForMinSdkVersion(
+                        "v2-only-unknown-pair-in-apk-sig-block.apk", AndroidSdkVersion.N));
+    }
+
+    @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
+        // known ones.
+        assertVerified(
+                verifyForMinSdkVersion(
+                        "v2-only-with-ignorable-unsupported-sig-algs.apk", AndroidSdkVersion.N));
+    }
+
+    @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
+        // emit an additional digest record with signature algorithm 0x12345678.
+        assertVerificationFailure(
+                "v2-only-signatures-and-digests-block-mismatch.apk",
+                Issue.V2_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
+        // certificate where the RSA modulus has a bitflip.
+        assertVerificationFailure(
+                "v2-only-cert-and-public-key-mismatch.apk",
+                Issue.V2_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.
+        assertVerificationFailure(
+                "v2-only-no-certs-in-sig.apk", Issue.V2_SIG_NO_CERTIFICATES);
+    }
+
+    @Test
+    public void testTwoSignersAccepted() throws Exception {
+        // APK signed by two different signers
+        assertVerified(verify("two-signers.apk"));
+        assertVerified(verify("v1-only-two-signers.apk"));
+        assertVerified(verifyForMinSdkVersion("v2-only-two-signers.apk", AndroidSdkVersion.N));
+    }
+
+    @Test
+    public void testV2TwoSignersRejectedWhenOneBroken() throws Exception {
+        // Bitflip in the ECDSA signature of second signer. Based on two-signers.apk.
+        // This asserts that breakage in any signer leads to rejection of the APK.
+        assertVerificationFailure(
+                "two-signers-second-signer-v2-broken.apk", Issue.V2_SIG_DID_NOT_VERIFY);
+    }
+
+    @Test
+    public void testV2TwoSignersRejectedWhenOneWithoutSignatures() throws Exception {
+        // APK v2-signed by two different signers. However, there are no signatures for the second
+        // signer.
+        assertVerificationFailure(
+                "v2-only-two-signers-second-signer-no-sig.apk", Issue.V2_SIG_NO_SIGNATURES);
+    }
+
+    @Test
+    public void testV2TwoSignersRejectedWhenOneWithoutSupportedSignatures() throws Exception {
+        // APK v2-signed by two different signers. However, there are no supported signatures for
+        // the second signer.
+        assertVerificationFailure(
+                "v2-only-two-signers-second-signer-no-supported-sig.apk",
+                Issue.V2_SIG_NO_SUPPORTED_SIGNATURES);
+    }
+
+    @Test
+    public void testCorrectCertUsedFromPkcs7SignedDataCertsSet() throws Exception {
+        // Obtained by prepending the rsa-1024 certificate to the PKCS#7 SignedData certificates set
+        // of v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-2048.apk META-INF/CERT.RSA. The certs
+        // (in the order of appearance in the file) are thus: rsa-1024, rsa-2048. The package's
+        // signing cert is rsa-2048.
+        ApkVerifier.Result result = verify("v1-only-pkcs7-cert-bag-first-cert-not-used.apk");
+        assertVerified(result);
+        List<X509Certificate> signingCerts = result.getSignerCertificates();
+        assertEquals(1, signingCerts.size());
+        assertEquals(
+                "fb5dbd3c669af9fc236c6991e6387b7f11ff0590997f22d0f5c74ff40e04fca8",
+                HexEncoding.encode(sha256(signingCerts.get(0).getEncoded())));
+    }
+
+    @Test
+    public void testV1SchemeSignatureCertNotReencoded() throws Exception {
+        // Regression test for b/30148997 and b/18228011. When PackageManager does not preserve the
+        // original encoded form of signing certificates, bad things happen, such as rejection of
+        // completely valid updates to apps. The issue in b/30148997 and b/18228011 was that
+        // PackageManager started re-encoding signing certs into DER. This normally produces exactly
+        // the original form because X.509 certificates are supposed to be DER-encoded. However, a
+        // small fraction of Android apps uses X.509 certificates which are not DER-encoded. For
+        // such apps, re-encoding into DER changes the serialized form of the certificate, creating
+        // a mismatch with the serialized form stored in the PackageManager database, leading to the
+        // rejection of updates for the app.
+        //
+        // v1-only-with-rsa-1024-cert-not-der.apk cert's signature is not DER-encoded. It is
+        // BER-encoded, with length encoded as two bytes instead of just one.
+        // v1-only-with-rsa-1024-cert-not-der.apk META-INF/CERT.RSA was obtained from
+        // v1-only-with-rsa-1024.apk META-INF/CERT.RSA by manually modifying the ASN.1 structure.
+        ApkVerifier.Result result = verify("v1-only-with-rsa-1024-cert-not-der.apk");
+
+        // On JDK 8u131 and newer, when the default (SUN) X.509 CertificateFactory implementation is
+        // used, PKCS #7 signature verification fails because the certificate is not DER-encoded.
+        // This contrived block of code disables this test in this scenario.
+        if (!result.isVerified()) {
+            List<ApkVerifier.Result.V1SchemeSignerInfo> signers = result.getV1SchemeSigners();
+            if (signers.size() > 0) {
+                ApkVerifier.Result.V1SchemeSignerInfo signer = signers.get(0);
+                for (IssueWithParams issue : signer.getErrors()) {
+                    if (issue.getIssue() == Issue.JAR_SIG_PARSE_EXCEPTION) {
+                        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+                        if ("SUN".equals(certFactory.getProvider().getName())) {
+                            Throwable exception = (Throwable) issue.getParams()[1];
+                            Throwable e = exception;
+                            while (e != null) {
+                                String msg = e.getMessage();
+                                e = e.getCause();
+                                if ((msg != null)
+                                        && (msg.contains("Redundant length bytes found"))) {
+                                    Assume.assumeNoException(exception);
+                                }
+                            }
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+
+        assertVerified(result);
+        List<X509Certificate> signingCerts = result.getSignerCertificates();
+        assertEquals(1, signingCerts.size());
+        assertEquals(
+                "c5d4535a7e1c8111687a8374b2198da6f5ff8d811a7a25aa99ef060669342fa9",
+                HexEncoding.encode(sha256(signingCerts.get(0).getEncoded())));
+    }
+
+    @Test
+    public void testV1SchemeSignatureCertNotReencoded2() throws Exception {
+        // Regression test for b/30148997 and b/18228011. When PackageManager does not preserve the
+        // original encoded form of signing certificates, bad things happen, such as rejection of
+        // completely valid updates to apps. The issue in b/30148997 and b/18228011 was that
+        // PackageManager started re-encoding signing certs into DER. This normally produces exactly
+        // the original form because X.509 certificates are supposed to be DER-encoded. However, a
+        // small fraction of Android apps uses X.509 certificates which are not DER-encoded. For
+        // such apps, re-encoding into DER changes the serialized form of the certificate, creating
+        // a mismatch with the serialized form stored in the PackageManager database, leading to the
+        // rejection of updates for the app.
+        //
+        // v1-only-with-rsa-1024-cert-not-der2.apk cert's signature is not DER-encoded. It is
+        // BER-encoded, with the BIT STRING value containing an extraneous leading 0x00 byte.
+        // v1-only-with-rsa-1024-cert-not-der2.apk META-INF/CERT.RSA was obtained from
+        // v1-only-with-rsa-1024.apk META-INF/CERT.RSA by manually modifying the ASN.1 structure.
+        ApkVerifier.Result result = verify("v1-only-with-rsa-1024-cert-not-der2.apk");
+        assertVerified(result);
+        List<X509Certificate> signingCerts = result.getSignerCertificates();
+        assertEquals(1, signingCerts.size());
+        assertEquals(
+                "da3da398de674541313deed77218ce94798531ea5131bb9b1bb4063ba4548cfb",
+                HexEncoding.encode(sha256(signingCerts.get(0).getEncoded())));
+    }
+
+    @Test
+    public void testMaxSizedZipEocdCommentAccepted() throws Exception {
+        // Obtained by modifying apksigner to produce a max-sized (0xffff bytes long) ZIP End of
+        // Central Directory comment, and signing the original.apk using the modified apksigner.
+        assertVerified(verify("v1-only-max-sized-eocd-comment.apk"));
+        assertVerified(
+                verifyForMinSdkVersion("v2-only-max-sized-eocd-comment.apk", AndroidSdkVersion.N));
+    }
+
+    @Test
+    public void testEmptyApk() throws Exception {
+        // Unsigned empty ZIP archive
+        try {
+            verifyForMinSdkVersion("empty-unsigned.apk", 1);
+            fail("ApkFormatException should've been thrown");
+        } catch (ApkFormatException expected) {}
+
+        // JAR-signed empty ZIP archive
+        try {
+            verifyForMinSdkVersion("v1-only-empty.apk", 18);
+            fail("ApkFormatException should've been thrown");
+        } catch (ApkFormatException expected) {}
+
+        // APK Signature Scheme v2 signed empty ZIP archive
+        try {
+            verifyForMinSdkVersion("v2-only-empty.apk", AndroidSdkVersion.N);
+            fail("ApkFormatException should've been thrown");
+        } catch (ApkFormatException expected) {}
+    }
+
+    @Test
+    public void testTargetSandboxVersion2AndHigher() throws Exception {
+        // This APK (and its variants below) use minSdkVersion 18, meaning it needs to be signed
+        // with v1 and v2 schemes
+
+        // This APK is signed with v1 and v2 schemes and thus should verify
+        assertVerified(verify("targetSandboxVersion-2.apk"));
+
+        // v1 signature is needed only if minSdkVersion is lower than 24
+        assertVerificationFailure(
+                verify("v2-only-targetSandboxVersion-2.apk"), Issue.JAR_SIG_NO_MANIFEST);
+        assertVerified(verifyForMinSdkVersion("v2-only-targetSandboxVersion-2.apk", 24));
+
+        // v2 signature is required
+        assertVerificationFailure(
+                verify("v1-only-targetSandboxVersion-2.apk"),
+                Issue.NO_SIG_FOR_TARGET_SANDBOX_VERSION);
+        assertVerificationFailure(
+                verify("unsigned-targetSandboxVersion-2.apk"),
+                Issue.NO_SIG_FOR_TARGET_SANDBOX_VERSION);
+
+        // minSdkVersion 28, meaning v1 signature not needed
+        assertVerified(verify("v2-only-targetSandboxVersion-3.apk"));
+    }
+
+    @Test
+    public void testV1MultipleDigestAlgsInManifestAndSignatureFile() throws Exception {
+        // MANIFEST.MF contains SHA-1 and SHA-256 digests for each entry, .SF contains only SHA-1
+        // digests. This file was obtained by:
+        //   jarsigner -sigalg SHA256withRSA -digestalg SHA-256 ... <file> ...
+        //   jarsigner -sigalg SHA1withRSA -digestalg SHA1 ... <same file> ...
+        assertVerified(verify("v1-sha1-sha256-manifest-and-sha1-sf.apk"));
+
+        // MANIFEST.MF and .SF contain SHA-1 and SHA-256 digests for each entry. This file was
+        // obtained by modifying apksigner to output multiple digests.
+        assertVerified(verify("v1-sha1-sha256-manifest-and-sf.apk"));
+
+        // One of the digests is wrong in either MANIFEST.MF or .SF. These files were obtained by
+        // modifying apksigner to output multiple digests and to flip a bit to create a wrong
+        // digest.
+
+        // SHA-1 digests in MANIFEST.MF are wrong, but SHA-256 digests are OK.
+        // The APK will fail to verify on API Level 17 and lower, but will verify on API Level 18
+        // and higher.
+        assertVerificationFailure(
+                verify("v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-manifest.apk"),
+                Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY);
+        assertVerificationFailure(
+                verifyForMaxSdkVersion(
+                        "v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-manifest.apk", 17),
+                Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY);
+        assertVerified(
+                verifyForMinSdkVersion(
+                        "v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-manifest.apk", 18));
+
+        // SHA-1 digests in .SF are wrong, but SHA-256 digests are OK.
+        // The APK will fail to verify on API Level 17 and lower, but will verify on API Level 18
+        // and higher.
+        assertVerificationFailure(
+                verify("v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-sf.apk"),
+                Issue.JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY);
+        assertVerificationFailure(
+                verifyForMaxSdkVersion(
+                        "v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-sf.apk", 17),
+                Issue.JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY);
+        assertVerified(
+                verifyForMinSdkVersion(
+                        "v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-sf.apk", 18));
+
+        // SHA-256 digests in MANIFEST.MF are wrong, but SHA-1 digests are OK.
+        // The APK will fail to verify on API Level 18 and higher, but will verify on API Level 17
+        // and lower.
+        assertVerificationFailure(
+                verify("v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-manifest.apk"),
+                Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY);
+        assertVerificationFailure(
+                verifyForMinSdkVersion(
+                        "v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-manifest.apk", 18),
+                Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY);
+        assertVerified(
+                verifyForMaxSdkVersion(
+                        "v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-manifest.apk", 17));
+
+        // SHA-256 digests in .SF are wrong, but SHA-1 digests are OK.
+        // The APK will fail to verify on API Level 18 and higher, but will verify on API Level 17
+        // and lower.
+        assertVerificationFailure(
+                verify("v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-sf.apk"),
+                Issue.JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY);
+        assertVerificationFailure(
+                verifyForMinSdkVersion(
+                        "v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-sf.apk", 18),
+                Issue.JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY);
+        assertVerified(
+                verifyForMaxSdkVersion(
+                        "v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-sf.apk", 17));
+    }
+
+    @Test
+    public void testV1WithUnsupportedCharacterInZipEntryName() throws Exception {
+        // Android Package Manager does not support ZIP entry names containing CR or LF
+        assertVerificationFailure(
+                verify("v1-only-with-cr-in-entry-name.apk"),
+                Issue.JAR_SIG_UNNNAMED_MANIFEST_SECTION);
+        assertVerificationFailure(
+                verify("v1-only-with-lf-in-entry-name.apk"),
+                Issue.JAR_SIG_UNNNAMED_MANIFEST_SECTION);
+    }
+
+    @Test
+    public void testWeirdZipCompressionMethod() throws Exception {
+        // 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.
+        assertVerified(verify("weird-compression-method.apk"));
+    }
+
+    @Test
+    public void testZipCompressionMethodMismatchBetweenLfhAndCd() throws Exception {
+        // Android Package Manager ignores compressionMethod field in Local File Header and always
+        // 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.
+        assertVerified(verify("mismatched-compression-method.apk"));
+    }
+
+    private ApkVerifier.Result verify(String apkFilenameInResources)
+            throws IOException, ApkFormatException, NoSuchAlgorithmException {
+        return verify(apkFilenameInResources, null, null);
+    }
+
+    private ApkVerifier.Result verifyForMinSdkVersion(
+            String apkFilenameInResources, int minSdkVersion)
+                    throws IOException, ApkFormatException, NoSuchAlgorithmException {
+        return verify(apkFilenameInResources, minSdkVersion, null);
+    }
+
+    private ApkVerifier.Result verifyForMaxSdkVersion(
+            String apkFilenameInResources, int maxSdkVersion)
+                    throws IOException, ApkFormatException, NoSuchAlgorithmException {
+        return verify(apkFilenameInResources, null, maxSdkVersion);
+    }
+
+    private ApkVerifier.Result verify(
+            String apkFilenameInResources,
+            Integer minSdkVersionOverride,
+            Integer maxSdkVersionOverride)
+                    throws IOException, ApkFormatException, NoSuchAlgorithmException {
+        byte[] apkBytes = Resources.toByteArray(getClass(), apkFilenameInResources);
+
+        ApkVerifier.Builder builder =
+                new ApkVerifier.Builder(DataSources.asDataSource(ByteBuffer.wrap(apkBytes)));
+        if (minSdkVersionOverride != null) {
+            builder.setMinCheckedPlatformVersion(minSdkVersionOverride);
+        }
+        if (maxSdkVersionOverride != null) {
+            builder.setMaxCheckedPlatformVersion(maxSdkVersionOverride);
+        }
+        return builder.build().verify();
+    }
+
+    static void assertVerified(ApkVerifier.Result result) {
+        if (result.isVerified()) {
+            return;
+        }
+
+        StringBuilder msg = new StringBuilder();
+        for (IssueWithParams issue : result.getErrors()) {
+            if (msg.length() > 0) {
+                msg.append('\n');
+            }
+            msg.append(issue);
+        }
+        for (ApkVerifier.Result.V1SchemeSignerInfo signer : result.getV1SchemeSigners()) {
+            String signerName = signer.getName();
+            for (IssueWithParams issue : signer.getErrors()) {
+                if (msg.length() > 0) {
+                    msg.append('\n');
+                }
+                msg.append("JAR signer ").append(signerName).append(": ").append(issue);
+            }
+        }
+        for (ApkVerifier.Result.V2SchemeSignerInfo signer : result.getV2SchemeSigners()) {
+            String signerName = "signer #" + (signer.getIndex() + 1);
+            for (IssueWithParams issue : signer.getErrors()) {
+                if (msg.length() > 0) {
+                    msg.append('\n');
+                }
+                msg.append("APK Signature Scheme v2 signer ")
+                        .append(signerName).append(": ").append(issue);
+            }
+        }
+
+        fail("APK did not verify: " + msg);
+    }
+
+    private void assertVerified(
+            String apkFilenameInResources,
+            Integer minSdkVersionOverride,
+            Integer maxSdkVersionOverride) throws Exception {
+        assertVerified(
+                verify(apkFilenameInResources, minSdkVersionOverride, maxSdkVersionOverride));
+    }
+
+    static void assertVerificationFailure(ApkVerifier.Result result, Issue expectedIssue) {
+        if (result.isVerified()) {
+            fail("APK verification succeeded instead of failing with " + expectedIssue);
+            return;
+        }
+
+        StringBuilder msg = new StringBuilder();
+        for (IssueWithParams issue : result.getErrors()) {
+            if (expectedIssue.equals(issue.getIssue())) {
+                return;
+            }
+            if (msg.length() > 0) {
+                msg.append('\n');
+            }
+            msg.append(issue);
+        }
+        for (ApkVerifier.Result.V1SchemeSignerInfo signer : result.getV1SchemeSigners()) {
+            String signerName = signer.getName();
+            for (ApkVerifier.IssueWithParams issue : signer.getErrors()) {
+                if (expectedIssue.equals(issue.getIssue())) {
+                    return;
+                }
+                if (msg.length() > 0) {
+                    msg.append('\n');
+                }
+                msg.append("JAR signer ").append(signerName).append(": ").append(issue);
+            }
+        }
+        for (ApkVerifier.Result.V2SchemeSignerInfo signer : result.getV2SchemeSigners()) {
+            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 v2 signer ")
+                        .append(signerName).append(": ").append(issue);
+            }
+        }
+
+        fail("APK failed verification for the wrong reason"
+                + " . Expected: " + expectedIssue + ", actual: " + msg);
+    }
+
+    private void assertVerificationFailure(
+            String apkFilenameInResources, ApkVerifier.Issue expectedIssue)
+                    throws Exception {
+        assertVerificationFailure(verify(apkFilenameInResources), expectedIssue);
+    }
+
+    private void assertVerifiedForEach(
+            String apkFilenamePatternInResources, String[] args) throws Exception {
+        assertVerifiedForEach(apkFilenamePatternInResources, args, null, null);
+    }
+
+    private void assertVerifiedForEach(
+            String apkFilenamePatternInResources,
+            String[] args,
+            Integer minSdkVersionOverride,
+            Integer maxSdkVersionOverride) throws Exception {
+        for (String arg : args) {
+            String apkFilenameInResources =
+                    String.format(Locale.US, apkFilenamePatternInResources, arg);
+            assertVerified(apkFilenameInResources, minSdkVersionOverride, maxSdkVersionOverride);
+        }
+    }
+
+    private void assertVerifiedForEachForMinSdkVersion(
+            String apkFilenameInResources, String[] args, int minSdkVersion)
+                    throws Exception {
+        assertVerifiedForEach(apkFilenameInResources, args, minSdkVersion, null);
+    }
+
+    private static byte[] sha256(byte[] msg) throws Exception {
+        try {
+            return MessageDigest.getInstance("SHA-256").digest(msg);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("Failed to create SHA-256 MessageDigest", e);
+        }
+    }
+
+    private static void assumeThatRsaPssAvailable() throws Exception {
+        Assume.assumeTrue(Security.getProviders("Signature.SHA256withRSA/PSS") != null);
+    }
+
+    private static void assumeThatMd5AcceptedInPkcs7Signature() throws Exception {
+        String algs = Security.getProperty("jdk.jar.disabledAlgorithms");
+        if ((algs != null) && (algs.toLowerCase(Locale.US).contains("md5"))) {
+            Assume.assumeNoException(
+                    new RuntimeException("MD5 not accepted in PKCS #7 signatures"
+                            + " . jdk.jar.disabledAlgorithms: \"" + algs + "\""));
+        }
+    }
+}
diff --git a/src/test/java/com/android/apksig/apk/AllTests.java b/src/test/java/com/android/apksig/apk/AllTests.java
new file mode 100644
index 0000000..64895ab
--- /dev/null
+++ b/src/test/java/com/android/apksig/apk/AllTests.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.apk;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+    ApkUtilsTest.class,
+})
+public class AllTests {}
diff --git a/src/test/java/com/android/apksig/internal/AllTests.java b/src/test/java/com/android/apksig/internal/AllTests.java
new file mode 100644
index 0000000..77d760e
--- /dev/null
+++ b/src/test/java/com/android/apksig/internal/AllTests.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.internal;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+    com.android.apksig.internal.asn1.AllTests.class,
+    com.android.apksig.internal.util.AllTests.class,
+})
+public class AllTests {}
diff --git a/src/test/java/com/android/apksig/internal/asn1/AllTests.java b/src/test/java/com/android/apksig/internal/asn1/AllTests.java
new file mode 100644
index 0000000..9f7265a
--- /dev/null
+++ b/src/test/java/com/android/apksig/internal/asn1/AllTests.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.internal.asn1;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+    com.android.apksig.internal.asn1.ber.AllTests.class
+})
+public class AllTests {}
diff --git a/src/test/java/com/android/apksig/internal/asn1/ber/AllTests.java b/src/test/java/com/android/apksig/internal/asn1/ber/AllTests.java
new file mode 100644
index 0000000..6916164
--- /dev/null
+++ b/src/test/java/com/android/apksig/internal/asn1/ber/AllTests.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.internal.asn1.ber;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+    BerDataValueTest.class,
+    ByteBufferBerDataValueReaderTest.class,
+    InputStreamBerDataValueReaderTest.class,
+})
+public class AllTests {}
diff --git a/src/test/java/com/android/apksig/internal/asn1/ber/BerDataValueReaderTestBase.java b/src/test/java/com/android/apksig/internal/asn1/ber/BerDataValueReaderTestBase.java
new file mode 100644
index 0000000..34d236b
--- /dev/null
+++ b/src/test/java/com/android/apksig/internal/asn1/ber/BerDataValueReaderTestBase.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.internal.asn1.ber;
+
+import static com.android.apksig.internal.test.MoreAsserts.assertByteBufferEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.apksig.internal.util.HexEncoding;
+import org.junit.Test;
+
+/**
+ * Base class for unit tests of ASN.1 BER (see {@code X.690}) data value reader implementations.
+ *
+ * <p>Subclasses need to provide only an implementation of {@link #createReader(byte[])} and
+ * subclass-specific tests.
+ */
+public abstract class BerDataValueReaderTestBase {
+
+    /**
+     * Returns a new reader initialized with the provided input.
+     */
+    protected abstract BerDataValueReader createReader(byte[] input);
+
+    @Test
+    public void testEmptyInput() throws Exception {
+        assertNull(readDataValue(""));
+    }
+
+    @Test
+    public void testEndOfInput() throws Exception {
+        BerDataValueReader reader = createReader("3000"); // SEQUENCE with empty contents
+        assertNotNull(reader.readDataValue());
+        // End of input has been reached
+        assertNull(reader.readDataValue());
+        // Null should also be returned on consecutive invocations
+        assertNull(reader.readDataValue());
+    }
+
+    @Test
+    public void testSingleByteTagId() throws Exception {
+        BerDataValue dataValue = readDataValue("1000");
+        assertEquals(BerEncoding.TAG_CLASS_UNIVERSAL, dataValue.getTagClass());
+        assertFalse(dataValue.isConstructed());
+        assertEquals(0x10, dataValue.getTagNumber());
+
+        dataValue = readDataValue("3900");
+        assertEquals(BerEncoding.TAG_CLASS_UNIVERSAL, dataValue.getTagClass());
+        assertTrue(dataValue.isConstructed());
+        assertEquals(0x19, dataValue.getTagNumber());
+
+        dataValue = readDataValue("6700");
+        assertEquals(BerEncoding.TAG_CLASS_APPLICATION, dataValue.getTagClass());
+        assertTrue(dataValue.isConstructed());
+        assertEquals(7, dataValue.getTagNumber());
+
+        dataValue = readDataValue("8600");
+        assertEquals(BerEncoding.TAG_CLASS_CONTEXT_SPECIFIC, dataValue.getTagClass());
+        assertFalse(dataValue.isConstructed());
+        assertEquals(6, dataValue.getTagNumber());
+
+        dataValue = readDataValue("fe00");
+        assertEquals(BerEncoding.TAG_CLASS_PRIVATE, dataValue.getTagClass());
+        assertTrue(dataValue.isConstructed());
+        assertEquals(0x1e, dataValue.getTagNumber());
+    }
+
+    @Test
+    public void testHighTagNumber() throws Exception {
+        assertEquals(7, readDataValue("3f0700").getTagNumber());
+        assertEquals(7, readDataValue("3f800700").getTagNumber());
+        assertEquals(7, readDataValue("3f80800700").getTagNumber());
+        assertEquals(7, readDataValue("3f8080800700").getTagNumber());
+        assertEquals(7, readDataValue("3f808080808080808080808080808080800700").getTagNumber());
+        assertEquals(375, readDataValue("3f827700").getTagNumber());
+        assertEquals(268435455, readDataValue("3fffffff7f00").getTagNumber());
+        assertEquals(Integer.MAX_VALUE, readDataValue("3f87ffffff7f00").getTagNumber());
+    }
+
+    @Test(expected = BerDataValueFormatException.class)
+    public void testHighTagNumberTooLarge() throws Exception {
+        readDataValue("3f888080800000"); // Integer.MAX_VALUE + 1
+    }
+
+    // @Test(expected = BerDataValueFormatException.class)
+    public void testTruncatedHighTagNumberLastOctetMissing() throws Exception {
+        readDataValue("9f80"); // terminating octet must not have the highest bit set
+    }
+
+    @Test(expected = BerDataValueFormatException.class)
+    public void testTruncatedBeforeFirstLengthOctet() throws Exception {
+        readDataValue("30");
+    }
+
+    @Test
+    public void testShortFormLength() throws Exception {
+        assertByteBufferEquals(new byte[0], readDataValue("3000").getEncodedContents());
+        assertByteBufferEquals(
+                HexEncoding.decode("010203"), readDataValue("3003010203").getEncodedContents());
+    }
+
+    @Test
+    public void testLongFormLength() throws Exception {
+        assertByteBufferEquals(new byte[0], readDataValue("308100").getEncodedContents());
+        assertByteBufferEquals(
+                HexEncoding.decode("010203"), readDataValue("30820003010203").getEncodedContents());
+        assertEquals(
+                255,
+                readDataValue(concat(HexEncoding.decode("3081ff"), new byte[255]))
+                        .getEncodedContents().remaining());
+        assertEquals(
+                0x110,
+                readDataValue(concat(HexEncoding.decode("30820110"), new byte[0x110]))
+                        .getEncodedContents().remaining());
+    }
+
+    @Test(expected = BerDataValueFormatException.class)
+    public void testTruncatedLongFormLengthBeforeFirstLengthByte() throws Exception {
+        readDataValue("3081");
+    }
+
+    @Test(expected = BerDataValueFormatException.class)
+    public void testTruncatedLongFormLengthLastLengthByteMissing() throws Exception {
+        readDataValue("308200");
+    }
+
+    @Test(expected = BerDataValueFormatException.class)
+    public void testLongFormLengthTooLarge() throws Exception {
+        readDataValue("3084ffffffff");
+    }
+
+    @Test
+    public void testIndefiniteFormLength() throws Exception {
+        assertByteBufferEquals(new byte[0], readDataValue("30800000").getEncodedContents());
+        assertByteBufferEquals(
+                HexEncoding.decode("010203"), readDataValue("30800102030000").getEncodedContents());
+        assertByteBufferEquals(
+                HexEncoding.decode(
+                        "000102030405060708090a0b0c0d0e0f"
+                            + "000102030405060708090a0b0c0d0e0f"
+                            + "000102030405060708090a0b0c0d0e0f"
+                            + "000102030405060708090a0b0c0d0e0f"
+                            + "000102030405060708090a0b0c0d0e0f"
+                            + "000102030405060708090a0b0c0d0e0f"
+                            + "000102030405060708090a0b0c0d0e0f"
+                            + "000102030405060708090a0b0c0d0e0f"
+                            + "000102030405060708090a0b0c0d0e0f"
+                            + "000102030405060708090a0b0c0d0e0f"
+                            + "000102030405060708090a0b0c0d0e0f"
+                            + "000102030405060708090a0b0c0d0e0f"
+                            + "000102030405060708090a0b0c0d0e0f"
+                            + "000102030405060708090a0b0c0d0e0f"
+                            + "000102030405060708090a0b0c0d0e0f"
+                            + "000102030405060708090a0b0c0d0e0f"
+                            + "000102030405060708090a0b0c0d0e0f"),
+                readDataValue(
+                        "3080"
+                                + "000102030405060708090a0b0c0d0e0f"
+                                + "000102030405060708090a0b0c0d0e0f"
+                                + "000102030405060708090a0b0c0d0e0f"
+                                + "000102030405060708090a0b0c0d0e0f"
+                                + "000102030405060708090a0b0c0d0e0f"
+                                + "000102030405060708090a0b0c0d0e0f"
+                                + "000102030405060708090a0b0c0d0e0f"
+                                + "000102030405060708090a0b0c0d0e0f"
+                                + "000102030405060708090a0b0c0d0e0f"
+                                + "000102030405060708090a0b0c0d0e0f"
+                                + "000102030405060708090a0b0c0d0e0f"
+                                + "000102030405060708090a0b0c0d0e0f"
+                                + "000102030405060708090a0b0c0d0e0f"
+                                + "000102030405060708090a0b0c0d0e0f"
+                                + "000102030405060708090a0b0c0d0e0f"
+                                + "000102030405060708090a0b0c0d0e0f"
+                                + "000102030405060708090a0b0c0d0e0f"
+                                + "0000"
+                        ).getEncodedContents());
+    }
+
+    @Test(expected = BerDataValueFormatException.class)
+    public void testDefiniteLengthContentsTruncatedBeforeFirstContentOctet() throws Exception {
+        readDataValue("3001");
+    }
+
+    @Test(expected = BerDataValueFormatException.class)
+    public void testIndefiniteLengthContentsTruncatedBeforeFirstContentOctet() throws Exception {
+        readDataValue("3080");
+    }
+
+    @Test(expected = BerDataValueFormatException.class)
+    public void testTruncatedDefiniteLengthContents() throws Exception {
+        readDataValue("30030102");
+    }
+
+    @Test(expected = BerDataValueFormatException.class)
+    public void testTruncatedIndefiniteLengthContents() throws Exception {
+        readDataValue("308001020300");
+    }
+
+    @Test
+    public void testEmptyDefiniteLengthContents() throws Exception {
+        assertByteBufferEquals(new byte[0], readDataValue("3000").getEncodedContents());
+    }
+
+    @Test
+    public void testEmptyIndefiniteLengthContents() throws Exception {
+        assertByteBufferEquals(new byte[0], readDataValue("30800000").getEncodedContents());
+    }
+
+    @Test
+    public void testReadAdvancesPosition() throws Exception {
+        BerDataValueReader reader = createReader("37018f050001020304");
+        assertByteBufferEquals(HexEncoding.decode("37018f"), reader.readDataValue().getEncoded());
+        assertByteBufferEquals(HexEncoding.decode("0500"), reader.readDataValue().getEncoded());
+        assertByteBufferEquals(HexEncoding.decode("01020304"), reader.readDataValue().getEncoded());
+        assertNull(reader.readDataValue());
+    }
+
+    private BerDataValueReader createReader(String hexEncodedInput) {
+        return createReader(HexEncoding.decode(hexEncodedInput));
+    }
+
+    private BerDataValue readDataValue(byte[] input)
+            throws BerDataValueFormatException {
+        return createReader(input).readDataValue();
+    }
+
+    private BerDataValue readDataValue(String hexEncodedInput)
+            throws BerDataValueFormatException {
+        return createReader(hexEncodedInput).readDataValue();
+    }
+
+    private static byte[] concat(byte[] arr1, byte[] arr2) {
+        byte[] result = new byte[arr1.length + arr2.length];
+        System.arraycopy(arr1,  0, result, 0, arr1.length);
+        System.arraycopy(arr2,  0, result, arr1.length, arr2.length);
+        return result;
+    }
+}
diff --git a/src/test/java/com/android/apksig/internal/asn1/ber/BerDataValueTest.java b/src/test/java/com/android/apksig/internal/asn1/ber/BerDataValueTest.java
new file mode 100644
index 0000000..5fd0ac3
--- /dev/null
+++ b/src/test/java/com/android/apksig/internal/asn1/ber/BerDataValueTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.internal.asn1.ber;
+
+import static com.android.apksig.internal.test.MoreAsserts.assertByteBufferEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import com.android.apksig.internal.util.HexEncoding;
+import java.nio.ByteBuffer;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class BerDataValueTest {
+    private static final BerDataValue TEST_VALUE1 =
+            new BerDataValue(
+                    ByteBuffer.wrap(HexEncoding.decode("aa")),
+                    ByteBuffer.wrap(HexEncoding.decode("bb")),
+                    BerEncoding.TAG_CLASS_UNIVERSAL,
+                    true,
+                    BerEncoding.TAG_NUMBER_SEQUENCE);
+
+    private static final BerDataValue TEST_VALUE2 =
+            new BerDataValue(
+                    ByteBuffer.wrap(HexEncoding.decode("cc")),
+                    ByteBuffer.wrap(HexEncoding.decode("dd")),
+                    BerEncoding.TAG_CLASS_CONTEXT_SPECIFIC,
+                    false,
+                    BerEncoding.TAG_NUMBER_OCTET_STRING);
+
+    @Test
+    public void testGetTagClass() {
+        assertEquals(BerEncoding.TAG_CLASS_UNIVERSAL, TEST_VALUE1.getTagClass());
+        assertEquals(BerEncoding.TAG_CLASS_CONTEXT_SPECIFIC, TEST_VALUE2.getTagClass());
+    }
+
+    @Test
+    public void testIsConstructed() {
+        assertTrue(TEST_VALUE1.isConstructed());
+        assertFalse(TEST_VALUE2.isConstructed());
+    }
+
+    @Test
+    public void testGetTagNumber() {
+        assertEquals(BerEncoding.TAG_NUMBER_SEQUENCE, TEST_VALUE1.getTagNumber());
+        assertEquals(BerEncoding.TAG_NUMBER_OCTET_STRING, TEST_VALUE2.getTagNumber());
+    }
+
+    @Test
+    public void testGetEncoded() {
+        assertByteBufferEquals(HexEncoding.decode("aa"), TEST_VALUE1.getEncoded());
+        assertByteBufferEquals(HexEncoding.decode("cc"), TEST_VALUE2.getEncoded());
+    }
+
+    @Test
+    public void testGetEncodedReturnsSlice() {
+        // Assert that changing the position of returned ByteBuffer does not affect ByteBuffers
+        // returned in the future
+        ByteBuffer encoded = TEST_VALUE1.getEncoded();
+        assertByteBufferEquals(HexEncoding.decode("aa"), encoded);
+        encoded.position(encoded.limit());
+        assertByteBufferEquals(HexEncoding.decode("aa"), TEST_VALUE1.getEncoded());
+    }
+
+    @Test
+    public void testGetEncodedContents() {
+        assertByteBufferEquals(HexEncoding.decode("bb"), TEST_VALUE1.getEncodedContents());
+        assertByteBufferEquals(HexEncoding.decode("dd"), TEST_VALUE2.getEncodedContents());
+    }
+
+    @Test
+    public void testGetEncodedContentsReturnsSlice() {
+        // Assert that changing the position of returned ByteBuffer does not affect ByteBuffers
+        // returned in the future
+        ByteBuffer encoded = TEST_VALUE1.getEncodedContents();
+        assertByteBufferEquals(HexEncoding.decode("bb"), encoded);
+        encoded.position(encoded.limit());
+        assertByteBufferEquals(HexEncoding.decode("bb"), TEST_VALUE1.getEncodedContents());
+    }
+
+    @Test
+    public void testDataValueReader() throws BerDataValueFormatException {
+        BerDataValueReader reader = TEST_VALUE1.dataValueReader();
+        assertSame(TEST_VALUE1, reader.readDataValue());
+        assertNull(reader.readDataValue());
+        assertNull(reader.readDataValue());
+    }
+
+    @Test
+    public void testContentsReader() throws BerDataValueFormatException {
+        BerDataValue dataValue =
+                new BerDataValue(
+                        ByteBuffer.allocate(0),
+                        ByteBuffer.wrap(HexEncoding.decode("300203040500")),
+                        BerEncoding.TAG_CLASS_UNIVERSAL,
+                        true,
+                        BerEncoding.TAG_NUMBER_SEQUENCE);
+        BerDataValueReader reader = dataValue.contentsReader();
+        assertEquals(ByteBufferBerDataValueReader.class, reader.getClass());
+        assertByteBufferEquals(HexEncoding.decode("30020304"), reader.readDataValue().getEncoded());
+        assertByteBufferEquals(HexEncoding.decode("0500"), reader.readDataValue().getEncoded());
+        assertNull(reader.readDataValue());
+    }
+}
diff --git a/src/test/java/com/android/apksig/internal/asn1/ber/ByteBufferBerDataValueReaderTest.java b/src/test/java/com/android/apksig/internal/asn1/ber/ByteBufferBerDataValueReaderTest.java
new file mode 100644
index 0000000..8875e5c
--- /dev/null
+++ b/src/test/java/com/android/apksig/internal/asn1/ber/ByteBufferBerDataValueReaderTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.internal.asn1.ber;
+
+import java.nio.ByteBuffer;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ByteBufferBerDataValueReaderTest extends BerDataValueReaderTestBase {
+
+    @Override
+    protected ByteBufferBerDataValueReader createReader(byte[] input) {
+        return new ByteBufferBerDataValueReader(ByteBuffer.wrap(input));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testConstructWithNullByteBuffer() throws Exception {
+        new ByteBufferBerDataValueReader(null);
+    }
+}
diff --git a/src/test/java/com/android/apksig/internal/asn1/ber/InputStreamBerDataValueReaderTest.java b/src/test/java/com/android/apksig/internal/asn1/ber/InputStreamBerDataValueReaderTest.java
new file mode 100644
index 0000000..97fcddf
--- /dev/null
+++ b/src/test/java/com/android/apksig/internal/asn1/ber/InputStreamBerDataValueReaderTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.internal.asn1.ber;
+
+import java.io.ByteArrayInputStream;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class InputStreamBerDataValueReaderTest extends BerDataValueReaderTestBase {
+
+    @Override
+    protected InputStreamBerDataValueReader createReader(byte[] input) {
+        return new InputStreamBerDataValueReader(new ByteArrayInputStream(input));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testConstructWithNullByteBuffer() throws Exception {
+        new InputStreamBerDataValueReader(null);
+    }
+}
diff --git a/src/test/java/com/android/apksig/internal/test/MoreAsserts.java b/src/test/java/com/android/apksig/internal/test/MoreAsserts.java
new file mode 100644
index 0000000..409e95e
--- /dev/null
+++ b/src/test/java/com/android/apksig/internal/test/MoreAsserts.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.internal.test;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import java.nio.ByteBuffer;
+
+public abstract class MoreAsserts {
+    private MoreAsserts() {}
+
+    /**
+     * Asserts that the contents of the provided {@code ByteBuffer} are as expected. This method
+     * does not change the position or the limit of the provided buffer.
+     */
+    public static void assertByteBufferEquals(byte[] expected, ByteBuffer actual) {
+        assertByteBufferEquals(null, expected, actual);
+    }
+
+    /**
+     * Asserts that the contents of the provided {@code ByteBuffer} are as expected. This method
+     * does not change the position or the limit of the provided buffer.
+     */
+    public static void assertByteBufferEquals(String message, byte[] expected, ByteBuffer actual) {
+        byte[] actualArr;
+        if ((actual.hasArray())
+                && (actual.arrayOffset() == 0) && (actual.array().length == actual.remaining())) {
+            actualArr = actual.array();
+        } else {
+            actualArr = new byte[actual.remaining()];
+            int actualOriginalPos = actual.position();
+            actual.get(actualArr);
+            actual.position(actualOriginalPos);
+        }
+        assertArrayEquals(message, expected, actualArr);
+    }
+}
diff --git a/src/test/java/com/android/apksig/internal/util/AllTests.java b/src/test/java/com/android/apksig/internal/util/AllTests.java
new file mode 100644
index 0000000..3043538
--- /dev/null
+++ b/src/test/java/com/android/apksig/internal/util/AllTests.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.internal.util;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+    ArrayBackedByteBufferSinkTest.class,
+    DirectByteBufferSinkTest.class,
+})
+public class AllTests {}
diff --git a/src/test/java/com/android/apksig/internal/util/ArrayBackedByteBufferSinkTest.java b/src/test/java/com/android/apksig/internal/util/ArrayBackedByteBufferSinkTest.java
new file mode 100644
index 0000000..e554e19
--- /dev/null
+++ b/src/test/java/com/android/apksig/internal/util/ArrayBackedByteBufferSinkTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.internal.util;
+
+import java.nio.ByteBuffer;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ArrayBackedByteBufferSinkTest extends ByteBufferSinkTestBase {
+    @Override
+    protected ByteBuffer createBuffer(int size) {
+        return ByteBuffer.allocate(size);
+    }
+}
diff --git a/src/test/java/com/android/apksig/internal/util/ByteBufferSinkTestBase.java b/src/test/java/com/android/apksig/internal/util/ByteBufferSinkTestBase.java
new file mode 100644
index 0000000..1258e81
--- /dev/null
+++ b/src/test/java/com/android/apksig/internal/util/ByteBufferSinkTestBase.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.internal.util;
+
+import com.android.apksig.util.DataSinkTestBase;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+public abstract class ByteBufferSinkTestBase extends DataSinkTestBase<ByteBufferSink> {
+
+    private static final int START_POS = 100;
+
+    protected abstract ByteBuffer createBuffer(int size);
+
+    @Override
+    protected CloseableWithDataSink<ByteBufferSink> createDataSink() {
+        ByteBuffer buf = createBuffer(1024);
+        // Use non-zero position and limit which isn't set to capacity to catch the implementation
+        // under test ignoring the initial position.
+        buf.position(START_POS);
+        buf.limit(buf.capacity() - 300);
+        return CloseableWithDataSink.of(new ByteBufferSink(buf));
+    }
+
+    @Override
+    protected ByteBuffer getContents(ByteBufferSink dataSink) throws IOException {
+        ByteBuffer buf = dataSink.getBuffer();
+        int oldPos = buf.position();
+        int oldLimit = buf.limit();
+        try {
+            buf.position(START_POS);
+            buf.limit(oldPos);
+            return buf.slice();
+        } finally {
+            buf.limit(oldLimit);
+            buf.position(oldPos);
+        }
+    }
+}
diff --git a/src/test/java/com/android/apksig/internal/util/ByteStreams.java b/src/test/java/com/android/apksig/internal/util/ByteStreams.java
new file mode 100644
index 0000000..bca3b08
--- /dev/null
+++ b/src/test/java/com/android/apksig/internal/util/ByteStreams.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.internal.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Utilities for byte arrays and I/O streams.
+ */
+public final class ByteStreams {
+    private ByteStreams() {}
+
+    /**
+     * Returns the data remaining in the provided input stream as a byte array
+     */
+    public static byte[] toByteArray(InputStream in) throws IOException {
+        ByteArrayOutputStream result = new ByteArrayOutputStream();
+        byte[] buf = new byte[16384];
+        int chunkSize;
+        while ((chunkSize = in.read(buf)) != -1) {
+            result.write(buf, 0, chunkSize);
+        }
+        return result.toByteArray();
+    }
+}
diff --git a/src/test/java/com/android/apksig/internal/util/DirectByteBufferSinkTest.java b/src/test/java/com/android/apksig/internal/util/DirectByteBufferSinkTest.java
new file mode 100644
index 0000000..0b8e975
--- /dev/null
+++ b/src/test/java/com/android/apksig/internal/util/DirectByteBufferSinkTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.internal.util;
+
+import java.nio.ByteBuffer;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class DirectByteBufferSinkTest extends ByteBufferSinkTestBase {
+    @Override
+    protected ByteBuffer createBuffer(int size) {
+        return ByteBuffer.allocateDirect(size);
+    }
+}
diff --git a/src/test/java/com/android/apksig/internal/util/HexEncoding.java b/src/test/java/com/android/apksig/internal/util/HexEncoding.java
new file mode 100644
index 0000000..5d75138
--- /dev/null
+++ b/src/test/java/com/android/apksig/internal/util/HexEncoding.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.internal.util;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Hexadecimal encoding where each byte is represented by two hexadecimal digits.
+ */
+public class HexEncoding {
+
+    /** Hidden constructor to prevent instantiation. */
+    private HexEncoding() {}
+
+    private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
+
+    /**
+     * Encodes the provided data as a hexadecimal string.
+     */
+    public static String encode(byte[] data) {
+        return encode(data, 0, data.length);
+    }
+
+    /**
+     * Encodes the provided data as a hexadecimal string.
+     */
+    public static String encode(byte[] data, int offset, int len) {
+      StringBuilder result = new StringBuilder(len * 2);
+      for (int i = 0; i < len; i++) {
+          byte b = data[offset + i];
+          result.append(HEX_DIGITS[(b >>> 4) & 0x0f]);
+          result.append(HEX_DIGITS[b & 0x0f]);
+      }
+      return result.toString();
+    }
+
+    /**
+     * Encodes the provided data as a hexadecimal string.
+     */
+    public static String encode(ByteBuffer buf) {
+        return encode(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining());
+    }
+
+    /**
+     * Decodes the provided hexadecimal string into an array of bytes.
+     */
+    public static byte[] decode(String encoded) {
+        // IMPLEMENTATION NOTE: Special care is taken to permit odd number of hexadecimal digits.
+        int resultLengthBytes = (encoded.length() + 1) / 2;
+        byte[] result = new byte[resultLengthBytes];
+        int resultOffset = 0;
+        int encodedCharOffset = 0;
+        if ((encoded.length() % 2) != 0) {
+            // Odd number of digits -- the first digit is the lower 4 bits of the first result byte.
+            result[resultOffset++] =
+                    (byte) getHexadecimalDigitValue(encoded.charAt(encodedCharOffset));
+            encodedCharOffset++;
+        }
+        for (int len = encoded.length(); encodedCharOffset < len; encodedCharOffset += 2) {
+          result[resultOffset++] = (byte)
+                  ((getHexadecimalDigitValue(encoded.charAt(encodedCharOffset)) << 4)
+                          | getHexadecimalDigitValue(encoded.charAt(encodedCharOffset + 1)));
+        }
+        return result;
+    }
+
+    private static int getHexadecimalDigitValue(char c) {
+        if ((c >= 'a') && (c <= 'f')) {
+            return (c - 'a') + 0x0a;
+        } else if ((c >= 'A') && (c <= 'F')) {
+            return (c - 'A') + 0x0a;
+        } else if ((c >= '0') && (c <= '9')) {
+            return c - '0';
+        } else {
+            throw new IllegalArgumentException(
+                    "Invalid hexadecimal digit at position : '"
+                            + c + "' (0x" + Integer.toHexString(c) + ")");
+        }
+    }
+}
diff --git a/src/test/java/com/android/apksig/internal/util/Resources.java b/src/test/java/com/android/apksig/internal/util/Resources.java
new file mode 100644
index 0000000..8a761ff
--- /dev/null
+++ b/src/test/java/com/android/apksig/internal/util/Resources.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.internal.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Assorted methods to obtaining test input from resources.
+ */
+public final class Resources {
+    private Resources() {}
+
+    public static byte[] toByteArray(Class<?> cls, String resourceName) throws IOException {
+        try (InputStream in = cls.getResourceAsStream(resourceName)) {
+            if (in == null) {
+                throw new IllegalArgumentException("Resource not found: " + resourceName);
+            }
+            return ByteStreams.toByteArray(in);
+        }
+    }
+
+    public static X509Certificate toCertificate(
+            Class <?> cls, String resourceName) throws IOException, CertificateException {
+        try (InputStream in = cls.getResourceAsStream(resourceName)) {
+            if (in == null) {
+                throw new IllegalArgumentException("Resource not found: " + resourceName);
+            }
+            return (X509Certificate)
+                    CertificateFactory.getInstance("X.509").generateCertificate(in);
+        }
+    }
+
+    public static List<X509Certificate> toCertificateChain(
+            Class <?> cls, String resourceName) throws IOException, CertificateException {
+        Collection<? extends Certificate> certs;
+        try (InputStream in = cls.getResourceAsStream(resourceName)) {
+            if (in == null) {
+                throw new IllegalArgumentException("Resource not found: " + resourceName);
+            }
+            certs = CertificateFactory.getInstance("X.509").generateCertificates(in);
+        }
+        List<X509Certificate> result = new ArrayList<>(certs.size());
+        for (Certificate cert : certs) {
+            result.add((X509Certificate) cert);
+        }
+        return result;
+    }
+
+    public static PrivateKey toPrivateKey(Class <?> cls, String resourceName)
+                    throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
+        int delimiterIndex = resourceName.indexOf('-');
+        if (delimiterIndex == -1) {
+            throw new IllegalArgumentException(
+                    "Failed to autodetect key algorithm from resource name: " + resourceName);
+        }
+        String keyAlgorithm = resourceName.substring(0, delimiterIndex).toUpperCase(Locale.US);
+        return toPrivateKey(cls, resourceName, keyAlgorithm);
+    }
+
+    public static PrivateKey toPrivateKey(
+            Class <?> cls, String resourceName, String keyAlgorithm)
+                    throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
+        byte[] encoded = toByteArray(cls, resourceName);
+
+        // Keep overly strictly linter happy by limiting what JCA KeyFactory algorithms are used
+        // here
+        KeyFactory keyFactory;
+        switch (keyAlgorithm.toUpperCase(Locale.US)) {
+            case "RSA":
+                keyFactory = KeyFactory.getInstance("rsa");
+                break;
+            case "DSA":
+                keyFactory = KeyFactory.getInstance("dsa");
+                break;
+            case "EC":
+                keyFactory = KeyFactory.getInstance("ec");
+                break;
+            default:
+                throw new InvalidKeySpecException("Unsupported key algorithm: " + keyAlgorithm);
+        }
+
+        return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded));
+    }
+}
diff --git a/src/test/java/com/android/apksig/util/AllTests.java b/src/test/java/com/android/apksig/util/AllTests.java
new file mode 100644
index 0000000..0af9b5a
--- /dev/null
+++ b/src/test/java/com/android/apksig/util/AllTests.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.util;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+    DataSinkFromOutputStreamTest.class,
+    DataSinkFromRAFTest.class,
+    DataSourceFromByteBufferTest.class,
+    DataSourceFromRAFChunkTest.class,
+    DataSourceFromRAFTest.class,
+    InMemoryDataSinkDataSourceTest.class,
+    InMemoryDataSinkTest.class,
+})
+public class AllTests {}
diff --git a/src/test/java/com/android/apksig/util/DataSinkFromOutputStreamTest.java b/src/test/java/com/android/apksig/util/DataSinkFromOutputStreamTest.java
new file mode 100644
index 0000000..ecb9a70
--- /dev/null
+++ b/src/test/java/com/android/apksig/util/DataSinkFromOutputStreamTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.util;
+
+import com.android.apksig.internal.util.OutputStreamDataSink;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for the {@link DataSink} returned by {@link DataSinks#asDataSink(java.io.OutputStream)}.
+ */
+@RunWith(JUnit4.class)
+public class DataSinkFromOutputStreamTest extends DataSinkTestBase<OutputStreamDataSink> {
+
+    @Override
+    protected CloseableWithDataSink<OutputStreamDataSink> createDataSink() {
+        return CloseableWithDataSink.of(
+                (OutputStreamDataSink) DataSinks.asDataSink(new ByteArrayOutputStream()));
+    }
+
+    @Override
+    protected ByteBuffer getContents(OutputStreamDataSink dataSink) throws IOException {
+        return ByteBuffer.wrap(((ByteArrayOutputStream) dataSink.getOutputStream()).toByteArray());
+    }
+}
diff --git a/src/test/java/com/android/apksig/util/DataSinkFromRAFTest.java b/src/test/java/com/android/apksig/util/DataSinkFromRAFTest.java
new file mode 100644
index 0000000..e4441df
--- /dev/null
+++ b/src/test/java/com/android/apksig/util/DataSinkFromRAFTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.util;
+
+import com.android.apksig.internal.util.RandomAccessFileDataSink;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for the {@link DataSink} returned by
+ * {@link DataSinks#asDataSink(java.io.RandomAccessFile)}.
+ */
+@RunWith(JUnit4.class)
+public class DataSinkFromRAFTest extends DataSinkTestBase<RandomAccessFileDataSink> {
+
+    @Override
+    protected CloseableWithDataSink<RandomAccessFileDataSink> createDataSink() throws IOException {
+        File tmp = File.createTempFile(DataSourceFromRAFTest.class.getSimpleName(), ".bin");
+        RandomAccessFile f = null;
+        try {
+            f = new RandomAccessFile(tmp, "rw");
+        } finally {
+            if (f == null) {
+                tmp.delete();
+            }
+        }
+        return CloseableWithDataSink.of(
+                (RandomAccessFileDataSink) DataSinks.asDataSink(f),
+                new DataSourceFromRAFTest.TmpFileCloseable(tmp, f));
+    }
+
+    @Override
+    protected ByteBuffer getContents(RandomAccessFileDataSink dataSink) throws IOException {
+        RandomAccessFile f = dataSink.getFile();
+        if (f.length() > Integer.MAX_VALUE) {
+            throw new IOException("File too large: " + f.length());
+        }
+        byte[] contents = new byte[(int) f.length()];
+        f.seek(0);
+        f.readFully(contents);
+        return ByteBuffer.wrap(contents);
+    }
+}
diff --git a/src/test/java/com/android/apksig/util/DataSinkTestBase.java b/src/test/java/com/android/apksig/util/DataSinkTestBase.java
new file mode 100644
index 0000000..4329f1e
--- /dev/null
+++ b/src/test/java/com/android/apksig/util/DataSinkTestBase.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import org.junit.Test;
+
+/**
+ * Base class for testing implementations of {@link DataSink}. This class tests the contract of
+ * {@code DataSink}.
+ *
+ * <p>To subclass, provide an implementation of {@link #createDataSink()} which returns the
+ * implementation of {@code DataSink} you want to test.
+ */
+public abstract class DataSinkTestBase<T extends DataSink> {
+    /**
+     * Returns a new {@link DataSink}.
+     */
+    protected abstract CloseableWithDataSink<T> createDataSink() throws IOException;
+
+    /**
+     * Returns the contents of the data sink.
+     */
+    protected abstract ByteBuffer getContents(T dataSink) throws IOException;
+
+    @Test
+    public void testConsumeFromArray() throws Exception {
+        try (CloseableWithDataSink<T> c = createDataSink()) {
+            T sink = c.getDataSink();
+            byte[] input = "abcdefg".getBytes(StandardCharsets.UTF_8);
+            sink.consume(input, 2, 3); // "cde"
+            sink.consume(input, 0, 1); // "a"
+            assertContentsEquals("cdea", sink);
+
+            // Zero-length chunks
+            sink.consume(input, 0, 0);
+            sink.consume(input, 1, 0);
+            sink.consume(input, input.length - 2, 0);
+            sink.consume(input, input.length - 1, 0);
+            sink.consume(input, input.length, 0);
+
+            // Invalid chunks
+            assertConsumeArrayThrowsIOOB(sink, input, -1, 0);
+            assertConsumeArrayThrowsIOOB(sink, input, -1, 3);
+            assertConsumeArrayThrowsIOOB(sink, input, 0, input.length + 1);
+            assertConsumeArrayThrowsIOOB(sink, input, input.length - 2, 4);
+            assertConsumeArrayThrowsIOOB(sink, input, input.length + 1, 0);
+            assertConsumeArrayThrowsIOOB(sink, input, input.length + 1, 1);
+
+            assertContentsEquals("cdea", sink);
+        }
+    }
+
+    @Test
+    public void testConsumeFromByteBuffer() throws Exception {
+        try (CloseableWithDataSink<T> c = createDataSink()) {
+            T sink = c.getDataSink();
+            ByteBuffer input = ByteBuffer.wrap("abcdefg".getBytes(StandardCharsets.UTF_8));
+            input.position(2);
+            input.limit(5);
+            sink.consume(input); // "cde"
+            assertEquals(5, input.position());
+            assertEquals(5, input.limit());
+
+            input.position(0);
+            input.limit(1);
+            sink.consume(input); // "a"
+            assertContentsEquals("cdea", sink);
+
+            // Empty input
+            sink.consume(input);
+            assertContentsEquals("cdea", sink);
+
+            // ByteBuffer which isn't backed by a byte[]
+            input = ByteBuffer.allocateDirect(2);
+            input.put((byte) 'X');
+            input.put((byte) 'Z');
+            input.flip();
+            sink.consume(input);
+
+            assertContentsEquals("cdeaXZ", sink);
+            assertEquals(2, input.position());
+            assertEquals(2, input.limit());
+
+            // Empty input
+            sink.consume(input);
+            assertContentsEquals("cdeaXZ", sink);
+        }
+    }
+
+    /**
+     * Returns the contents of the provided buffer as a string. The buffer's position and limit
+     * remain unchanged.
+     */
+    private static String toString(ByteBuffer buf) {
+        return DataSourceTestBase.toString(buf);
+    }
+
+    private void assertContentsEquals(String expectedContents, T sink) throws IOException {
+        ByteBuffer actual = getContents(sink);
+        assertEquals(expectedContents, toString(actual));
+    }
+
+    private static void assertConsumeArrayThrowsIOOB(
+            DataSink sink, byte[] arr, int offset, int length) throws IOException {
+        try {
+            sink.consume(arr, offset, length);
+            fail();
+        } catch (IndexOutOfBoundsException expected) {}
+    }
+
+    public static class CloseableWithDataSink<T extends DataSink> implements Closeable {
+        private final T mDataSink;
+        private final Closeable mCloseable;
+
+        private CloseableWithDataSink(T dataSink, Closeable closeable) {
+            mDataSink = dataSink;
+            mCloseable = closeable;
+        }
+
+        public static <T extends DataSink> CloseableWithDataSink<T> of(T dataSink) {
+            return new CloseableWithDataSink<>(dataSink, null);
+        }
+
+        public static <T extends DataSink> CloseableWithDataSink<T> of(
+                T dataSink, Closeable closeable) {
+            return new CloseableWithDataSink<>(dataSink, closeable);
+        }
+
+        public T getDataSink() {
+            return mDataSink;
+        }
+
+        public Closeable getCloseable() {
+            return mCloseable;
+        }
+
+        @Override
+        public void close() throws IOException {
+            if (mCloseable != null) {
+                mCloseable.close();
+            }
+        }
+    }
+}
diff --git a/src/test/java/com/android/apksig/util/DataSourceFromByteBufferTest.java b/src/test/java/com/android/apksig/util/DataSourceFromByteBufferTest.java
new file mode 100644
index 0000000..83af814
--- /dev/null
+++ b/src/test/java/com/android/apksig/util/DataSourceFromByteBufferTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.util;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for the {@link DataSource} returned by {@link DataSources#asDataSource(ByteBuffer)}.
+ */
+@RunWith(JUnit4.class)
+public class DataSourceFromByteBufferTest extends DataSourceTestBase {
+
+    @Test
+    public void testChangesToBufferPosAndLimitNotVisible() throws Exception {
+        ByteBuffer buf = ByteBuffer.wrap("abcdefgh".getBytes(StandardCharsets.UTF_8));
+        buf.position(1);
+        buf.limit(4);
+        DataSource ds = DataSources.asDataSource(buf);
+        buf.position(2);
+        buf.limit(buf.capacity());
+        assertGetByteBufferEquals("bcd", ds, 0, (int) ds.size());
+        assertFeedEquals("bcd", ds, 0, (int) ds.size());
+        assertSliceEquals("bcd", ds, 0, (int) ds.size());
+        assertCopyToEquals("bcd", ds, 0, (int) ds.size());
+    }
+
+    @Override
+    protected CloseableWithDataSource createDataSource(byte[] contents) throws IOException {
+        return CloseableWithDataSource.of(DataSources.asDataSource(ByteBuffer.wrap(contents)));
+    }
+}
diff --git a/src/test/java/com/android/apksig/util/DataSourceFromRAFChunkTest.java b/src/test/java/com/android/apksig/util/DataSourceFromRAFChunkTest.java
new file mode 100644
index 0000000..8116012
--- /dev/null
+++ b/src/test/java/com/android/apksig/util/DataSourceFromRAFChunkTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.util;
+
+import com.android.apksig.util.DataSourceFromRAFTest.TmpFileCloseable;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for the {@link DataSource} returned by
+ * {@link DataSources#asDataSource(RandomAccessFile, long, long)}.
+ */
+@RunWith(JUnit4.class)
+public class DataSourceFromRAFChunkTest extends DataSourceTestBase {
+
+    @Test
+    public void testFileSizeChangesNotVisible() throws Exception {
+        try (CloseableWithDataSource c = createDataSource("abcdefg")) {
+            DataSource ds = c.getDataSource();
+            DataSource slice = ds.slice(3, 2);
+            File f = ((TmpFileCloseable) c.getCloseable()).getFile();
+            assertGetByteBufferEquals("abcdefg", ds, 0, (int) ds.size());
+            assertGetByteBufferEquals("de", slice, 0, (int) slice.size());
+            assertFeedEquals("cdefg", ds, 2, 5);
+            assertFeedEquals("e", slice, 1, 1);
+            assertCopyToEquals("cdefg", ds, 2, 5);
+            assertCopyToEquals("e", slice, 1, 1);
+            assertSliceEquals("cdefg", ds, 2, 5);
+            assertSliceEquals("e", slice, 1, 1);
+            try (RandomAccessFile raf = new RandomAccessFile(f, "rw")) {
+                raf.seek(raf.length());
+                raf.write("hijkl".getBytes(StandardCharsets.UTF_8));
+            }
+
+            assertGetByteBufferEquals("abcdefg", ds, 0, (int) ds.size());
+            assertGetByteBufferEquals("de", slice, 0, (int) slice.size());
+            assertGetByteBufferThrowsIOOB(ds, 0, (int) ds.size() + 3);
+            assertFeedThrowsIOOB(ds, 0, (int) ds.size() + 3);
+            assertSliceThrowsIOOB(ds, 0, (int) ds.size() + 3);
+            assertCopyToThrowsIOOB(ds, 0, (int) ds.size() + 3);
+        }
+    }
+
+    @Override
+    protected CloseableWithDataSource createDataSource(byte[] contents) throws IOException {
+        // "01" | contents | "9"
+        byte[] fullContents = new byte[2 + contents.length + 1];
+        fullContents[0] = '0';
+        fullContents[1] = '1';
+        System.arraycopy(contents, 0, fullContents, 2, contents.length);
+        fullContents[fullContents.length - 1] = '9';
+
+        File tmp = File.createTempFile(DataSourceFromRAFChunkTest.class.getSimpleName(), ".bin");
+        RandomAccessFile f = null;
+        try {
+            Files.write(tmp.toPath(), fullContents);
+            f = new RandomAccessFile(tmp, "r");
+        } finally {
+            if (f == null) {
+                tmp.delete();
+            }
+        }
+
+        return CloseableWithDataSource.of(
+                DataSources.asDataSource(f, 2, contents.length),
+                new DataSourceFromRAFTest.TmpFileCloseable(tmp, f));
+    }
+}
diff --git a/src/test/java/com/android/apksig/util/DataSourceFromRAFTest.java b/src/test/java/com/android/apksig/util/DataSourceFromRAFTest.java
new file mode 100644
index 0000000..36ef760
--- /dev/null
+++ b/src/test/java/com/android/apksig/util/DataSourceFromRAFTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.util;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for the {@link DataSource} returned by
+ * {@link DataSources#asDataSource(java.io.RandomAccessFile)}.
+ */
+@RunWith(JUnit4.class)
+public class DataSourceFromRAFTest extends DataSourceTestBase {
+
+    @Test
+    public void testFileSizeChangesVisible() throws Exception {
+        try (CloseableWithDataSource c = createDataSource("abcdefg")) {
+            DataSource ds = c.getDataSource();
+            DataSource slice = ds.slice(3, 2);
+            File f = ((TmpFileCloseable) c.getCloseable()).getFile();
+            assertGetByteBufferEquals("abcdefg", ds, 0, (int) ds.size());
+            assertGetByteBufferEquals("de", slice, 0, (int) slice.size());
+            assertFeedEquals("cdefg", ds, 2, 5);
+            assertFeedEquals("e", slice, 1, 1);
+            assertCopyToEquals("cdefg", ds, 2, 5);
+            assertCopyToEquals("e", slice, 1, 1);
+            assertSliceEquals("cdefg", ds, 2, 5);
+            assertSliceEquals("e", slice, 1, 1);
+            try (RandomAccessFile raf = new RandomAccessFile(f, "rw")) {
+                raf.seek(7);
+                raf.write("hijkl".getBytes(StandardCharsets.UTF_8));
+            }
+
+            assertEquals(12, ds.size());
+            assertGetByteBufferEquals("abcdefghijkl", ds, 0, (int) ds.size());
+            assertGetByteBufferEquals("de", slice, 0, (int) slice.size());
+            assertFeedEquals("cdefg", ds, 2, 5);
+            assertFeedEquals("fgh", ds, 5, 3);
+            assertCopyToEquals("fgh", ds, 5, 3);
+            assertSliceEquals("fgh", ds, 5, 3);
+        }
+    }
+
+    @Override
+    protected CloseableWithDataSource createDataSource(byte[] contents) throws IOException {
+        File tmp = File.createTempFile(DataSourceFromRAFTest.class.getSimpleName(), ".bin");
+        RandomAccessFile f = null;
+        try {
+            Files.write(tmp.toPath(), contents);
+            f = new RandomAccessFile(tmp, "r");
+        } finally {
+            if (f == null) {
+                tmp.delete();
+            }
+        }
+
+        return CloseableWithDataSource.of(
+                DataSources.asDataSource(f),
+                new TmpFileCloseable(tmp, f));
+    }
+
+    /**
+     * {@link Closeable} which closes the delegate {@code Closeable} and deletes the provided file.
+     */
+    static class TmpFileCloseable implements Closeable {
+        private final File mFile;
+        private final Closeable mDelegate;
+
+        TmpFileCloseable(File file, Closeable closeable) {
+            mFile = file;
+            mDelegate = closeable;
+        }
+
+        File getFile() {
+            return mFile;
+        }
+
+        @Override
+        public void close() throws IOException {
+            try {
+                if (mDelegate != null) {
+                    mDelegate.close();
+                }
+            } finally {
+                if (mFile != null) {
+                    mFile.delete();
+                }
+            }
+        }
+    }
+}
diff --git a/src/test/java/com/android/apksig/util/DataSourceTestBase.java b/src/test/java/com/android/apksig/util/DataSourceTestBase.java
new file mode 100644
index 0000000..a21f1e5
--- /dev/null
+++ b/src/test/java/com/android/apksig/util/DataSourceTestBase.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import org.junit.Test;
+
+/**
+ * Base class for testing implementations of {@link DataSource}. This class tests the contract of
+ * {@code DataSource}.
+ *
+ * <p>To subclass, provide an implementation of {@link #createDataSource(byte[])} which returns
+ * the implementation of {@code DataSource} you want to test.
+ */
+public abstract class DataSourceTestBase {
+
+    /**
+     * Returns a new {@link DataSource} containing the provided contents.
+     */
+    protected abstract CloseableWithDataSource createDataSource(byte[] contents) throws IOException;
+
+    protected CloseableWithDataSource createDataSource(String contents) throws IOException {
+        return createDataSource(contents.getBytes(StandardCharsets.UTF_8));
+    }
+
+    @Test
+    public void testSize() throws Exception {
+        try (CloseableWithDataSource c = createDataSource("Hello12345")) {
+            DataSource ds = c.getDataSource();
+            assertEquals(10, ds.size());
+        }
+    }
+
+    @Test
+    public void testSlice() throws Exception {
+        try (CloseableWithDataSource c = createDataSource("Hello12345")) {
+            DataSource ds = c.getDataSource();
+            assertSliceEquals("123", ds, 5, 3);
+            DataSource slice = ds.slice(3, 5);
+            assertGetByteBufferEquals("lo123", slice, 0, 5);
+
+            // Zero-length slices
+            assertSliceEquals("", ds, 0, 0);
+            assertSliceEquals("", ds, 1, 0);
+            assertSliceEquals("", ds, ds.size() - 2, 0);
+            assertSliceEquals("", ds, ds.size() - 1, 0);
+            assertSliceEquals("", ds, ds.size(), 0);
+            assertSliceEquals("", slice, 0, 0);
+            assertSliceEquals("", slice, 1, 0);
+            assertSliceEquals("", slice, slice.size() - 2, 0);
+            assertSliceEquals("", slice, slice.size() - 1, 0);
+            assertSliceEquals("", slice, slice.size(), 0);
+
+            // Invalid slices
+            assertSliceThrowsIOOB(ds, -1, 0);
+            assertSliceThrowsIOOB(slice, -1, 0);
+            assertSliceThrowsIOOB(ds, -1, 2);
+            assertSliceThrowsIOOB(slice, -1, 2);
+            assertSliceThrowsIOOB(ds, -1, 20);
+            assertSliceThrowsIOOB(slice, -1, 20);
+            assertSliceThrowsIOOB(ds, 1, 20);
+            assertSliceThrowsIOOB(slice, 1, 20);
+            assertSliceThrowsIOOB(ds, ds.size() + 1, 0);
+            assertSliceThrowsIOOB(slice, slice.size() + 1, 0);
+            assertSliceThrowsIOOB(ds, ds.size(), 1);
+            assertSliceThrowsIOOB(slice, slice.size(), 1);
+            assertSliceThrowsIOOB(ds, ds.size() - 1, -1);
+            assertSliceThrowsIOOB(ds, slice.size() - 1, -1);
+        }
+    }
+
+    @Test
+    public void testGetByteBuffer() throws Exception {
+        try (CloseableWithDataSource c = createDataSource("test1234")) {
+            DataSource ds = c.getDataSource();
+            assertGetByteBufferEquals("s", ds, 2, 1);
+            DataSource slice = ds.slice(3, 4); // "t123"
+            assertGetByteBufferEquals("2", slice, 2, 1);
+
+            // Zero-length chunks
+            assertEquals(0, ds.getByteBuffer(0, 0).capacity());
+            assertEquals(0, ds.getByteBuffer(ds.size(), 0).capacity());
+            assertEquals(0, ds.getByteBuffer(ds.size() - 1, 0).capacity());
+            assertEquals(0, ds.getByteBuffer(ds.size() - 2, 0).capacity());
+            assertEquals(0, slice.getByteBuffer(0, 0).capacity());
+            assertEquals(0, slice.getByteBuffer(slice.size(), 0).capacity());
+            assertEquals(0, slice.getByteBuffer(slice.size() - 1, 0).capacity());
+            assertEquals(0, slice.getByteBuffer(slice.size() - 2, 0).capacity());
+
+            // Invalid chunks
+            assertGetByteBufferThrowsIOOB(ds, -1, 0);
+            assertGetByteBufferThrowsIOOB(slice, -1, 0);
+            assertGetByteBufferThrowsIOOB(ds, -1, 2);
+            assertGetByteBufferThrowsIOOB(slice, -1, 2);
+            assertGetByteBufferThrowsIOOB(ds, -1, 20);
+            assertGetByteBufferThrowsIOOB(slice, -1, 20);
+            assertGetByteBufferThrowsIOOB(ds, 1, 20);
+            assertGetByteBufferThrowsIOOB(slice, 1, 20);
+            assertGetByteBufferThrowsIOOB(ds, ds.size() + 1, 0);
+            assertGetByteBufferThrowsIOOB(slice, slice.size() + 1, 0);
+            assertGetByteBufferThrowsIOOB(ds, ds.size(), 1);
+            assertGetByteBufferThrowsIOOB(slice, slice.size(), 1);
+            assertGetByteBufferThrowsIOOB(ds, ds.size() - 1, -1);
+            assertGetByteBufferThrowsIOOB(ds, slice.size() - 1, -1);
+        }
+    }
+
+    @Test
+    public void testFeed() throws Exception {
+        try (CloseableWithDataSource c = createDataSource("test1234")) {
+            DataSource ds = c.getDataSource();
+            assertFeedEquals("23", ds, 5, 2);
+            DataSource slice = ds.slice(1, 5); // "est12"
+            assertFeedEquals("t", slice, 2, 1);
+
+            // Zero-length chunks
+            assertFeedEquals("", ds, 0, 0);
+            assertFeedEquals("", ds, 1, 0);
+            assertFeedEquals("", ds, ds.size() - 2, 0);
+            assertFeedEquals("", ds, ds.size() - 1, 0);
+            assertFeedEquals("", ds, ds.size(), 0);
+            assertFeedEquals("", slice, 0, 0);
+            assertFeedEquals("", slice, 2, 0);
+            assertFeedEquals("", slice, slice.size() - 2, 0);
+            assertFeedEquals("", slice, slice.size() - 1, 0);
+            assertFeedEquals("", slice, slice.size(), 0);
+
+            // Invalid chunks
+            assertFeedThrowsIOOB(ds, -1, 0);
+            assertFeedThrowsIOOB(slice, -1, 0);
+            assertFeedThrowsIOOB(ds, -1, 2);
+            assertFeedThrowsIOOB(slice, -1, 2);
+            assertFeedThrowsIOOB(ds, -1, 10);
+            assertFeedThrowsIOOB(slice, -1, 10);
+            assertFeedThrowsIOOB(ds, 1, 10);
+            assertFeedThrowsIOOB(slice, 1, 10);
+            assertFeedThrowsIOOB(ds, ds.size() + 1, 0);
+            assertFeedThrowsIOOB(slice, slice.size() + 1, 0);
+            assertFeedThrowsIOOB(ds, ds.size(), 1);
+            assertFeedThrowsIOOB(slice, slice.size(), 1);
+            assertFeedThrowsIOOB(ds, ds.size() - 1, -1);
+            assertFeedThrowsIOOB(ds, slice.size() - 1, -1);
+        }
+    }
+
+    @Test
+    public void testCopyTo() throws Exception {
+        try (CloseableWithDataSource c = createDataSource("abcdefghijklmnop")) {
+            DataSource ds = c.getDataSource();
+            assertCopyToEquals("fgh", ds, 5, 3);
+            DataSource slice = ds.slice(2, 7); // "cdefghi"
+            assertCopyToEquals("efgh", slice, 2, 4);
+
+            // Zero-length chunks
+            assertCopyToEquals("", ds, 0, 0);
+            assertCopyToEquals("", ds, 1, 0);
+            assertCopyToEquals("", ds, ds.size() - 2, 0);
+            assertCopyToEquals("", ds, ds.size() - 1, 0);
+            assertCopyToEquals("", ds, ds.size(), 0);
+            assertCopyToEquals("", slice, 0, 0);
+            assertCopyToEquals("", slice, 2, 0);
+            assertCopyToEquals("", slice, slice.size() - 2, 0);
+            assertCopyToEquals("", slice, slice.size() - 1, 0);
+            assertCopyToEquals("", slice, slice.size(), 0);
+
+            // Invalid chunks
+            assertCopyToThrowsIOOB(ds, -1, 0);
+            assertCopyToThrowsIOOB(slice, -1, 0);
+            assertCopyToThrowsIOOB(ds, -1, 2);
+            assertCopyToThrowsIOOB(slice, -1, 2);
+            assertCopyToThrowsIOOB(ds, -1, 20);
+            assertCopyToThrowsIOOB(slice, -1, 20);
+            assertCopyToThrowsIOOB(ds, 1, 20);
+            assertCopyToThrowsIOOB(slice, 1, 20);
+            assertCopyToThrowsIOOB(ds, ds.size() + 1, 0);
+            assertCopyToThrowsIOOB(slice, slice.size() + 1, 0);
+            assertCopyToThrowsIOOB(ds, ds.size(), 1);
+            assertCopyToThrowsIOOB(slice, slice.size(), 1);
+            assertCopyToThrowsIOOB(ds, ds.size() - 1, -1);
+            assertCopyToThrowsIOOB(ds, slice.size() - 1, -1);
+
+            // Destination buffer too small
+            ByteBuffer buf = ByteBuffer.allocate(5);
+            buf.position(2);
+            buf.limit(3);
+            assertCopyToThrowsBufferOverflow(ds, 0, 2, buf);
+            buf.position(2);
+            buf.limit(3);
+            assertCopyToThrowsBufferOverflow(slice, 1, 2, buf);
+
+            // Destination buffer larger than chunk copied using copyTo
+            buf = ByteBuffer.allocate(10);
+            buf.position(2);
+            assertCopyToEquals("bcd", ds, 1, 3, buf);
+            buf = ByteBuffer.allocate(10);
+            buf.position(2);
+            assertCopyToEquals("fg", slice, 3, 2, buf);
+        }
+    }
+
+    protected static void assertSliceEquals(
+            String expectedContents, DataSource ds, long offset, int size) throws IOException {
+        DataSource slice = ds.slice(offset, size);
+        assertEquals(size, slice.size());
+        assertGetByteBufferEquals(expectedContents, slice, 0, size);
+    }
+
+    protected static void assertSliceThrowsIOOB(DataSource ds, long offset, int size) {
+        try {
+            ds.slice(offset, size);
+            fail();
+        } catch (IndexOutOfBoundsException expected) {}
+    }
+
+    protected static void assertGetByteBufferEquals(
+            String expectedContents, DataSource ds, long offset, int size) throws IOException {
+        ByteBuffer buf = ds.getByteBuffer(offset, size);
+        assertEquals(0, buf.position());
+        assertEquals(size, buf.limit());
+        assertEquals(size, buf.capacity());
+        assertEquals(expectedContents, toString(buf));
+    }
+
+    protected static void assertGetByteBufferThrowsIOOB(DataSource ds, long offset, int size)
+            throws IOException {
+        try {
+            ds.getByteBuffer(offset, size);
+            fail();
+        } catch (IndexOutOfBoundsException expected) {}
+    }
+
+    protected static void assertFeedEquals(
+            String expectedFedContents, DataSource ds, long offset, int size) throws IOException {
+        ReadableDataSink out = DataSinks.newInMemoryDataSink(size);
+        ds.feed(offset, size, out);
+        assertEquals(size, out.size());
+        assertEquals(expectedFedContents, toString(out.getByteBuffer(0, size)));
+    }
+
+    protected static void assertFeedThrowsIOOB(DataSource ds, long offset, long size)
+            throws IOException {
+        try {
+            ds.feed(offset, size, NullDataSink.INSTANCE);
+            fail();
+        } catch (IndexOutOfBoundsException expected) {}
+    }
+
+    protected static void assertCopyToEquals(
+            String expectedContents, DataSource ds, long offset, int size) throws IOException {
+        // Create a ByteBuffer backed by a section of a byte array. The ByteBuffer is on purpose not
+        // starting at offset 0 to catch issues to do with not checking ByteBuffer.arrayOffset().
+        byte[] arr = new byte[size + 10];
+        ByteBuffer buf = ByteBuffer.wrap(arr, 1, size + 5);
+        // Use non-zero position to catch issues with not checking buf.position()
+        buf.position(2);
+        // Buffer contains sufficient space for the requested copyTo operation
+        assertEquals(size + 4, buf.remaining());
+        assertCopyToEquals(expectedContents, ds, offset, size, buf);
+    }
+
+    private static void assertCopyToEquals(
+            String expectedContents, DataSource ds, long offset, int size, ByteBuffer buf)
+                    throws IOException {
+        int oldPosition = buf.position();
+        int oldLimit = buf.limit();
+        ds.copyTo(offset, size, buf);
+        // Position should've advanced by size whereas limit should've remained unchanged
+        assertEquals(oldPosition + size, buf.position());
+        assertEquals(oldLimit, buf.limit());
+
+        buf.limit(buf.position());
+        buf.position(oldPosition);
+        assertEquals(expectedContents, toString(buf));
+    }
+
+    protected static void assertCopyToThrowsIOOB(DataSource ds, long offset, int size)
+            throws IOException {
+        ByteBuffer buf = ByteBuffer.allocate((size < 0) ? 0 : size);
+        try {
+            ds.copyTo(offset, size, buf);
+            fail();
+        } catch (IndexOutOfBoundsException expected) {}
+    }
+
+    private static void assertCopyToThrowsBufferOverflow(
+            DataSource ds, long offset, int size, ByteBuffer buf) throws IOException {
+        try {
+            ds.copyTo(offset, size, buf);
+            fail();
+        } catch (BufferOverflowException expected) {}
+    }
+
+    /**
+     * Returns the contents of the provided buffer as a string. The buffer's position and limit
+     * remain unchanged.
+     */
+    static String toString(ByteBuffer buf) {
+        byte[] arr;
+        int offset;
+        int size = buf.remaining();
+        if (buf.hasArray()) {
+            arr = buf.array();
+            offset = buf.arrayOffset() + buf.position();
+        } else {
+            arr = new byte[buf.remaining()];
+            offset = 0;
+            int oldPos = buf.position();
+            buf.get(arr);
+            buf.position(oldPos);
+        }
+        return new String(arr, offset, size, StandardCharsets.UTF_8);
+    }
+
+    public static class CloseableWithDataSource implements Closeable {
+        private final DataSource mDataSource;
+        private final Closeable mCloseable;
+
+        private CloseableWithDataSource(DataSource dataSource, Closeable closeable) {
+            mDataSource = dataSource;
+            mCloseable = closeable;
+        }
+
+        public static CloseableWithDataSource of(DataSource dataSource) {
+            return new CloseableWithDataSource(dataSource, null);
+        }
+
+        public static CloseableWithDataSource of(DataSource dataSource, Closeable closeable) {
+            return new CloseableWithDataSource(dataSource, closeable);
+        }
+
+        public DataSource getDataSource() {
+            return mDataSource;
+        }
+
+        public Closeable getCloseable() {
+            return mCloseable;
+        }
+
+        @Override
+        public void close() throws IOException {
+            if (mCloseable != null) {
+                mCloseable.close();
+            }
+        }
+    }
+
+    private static final class NullDataSink implements DataSink {
+        private static final NullDataSink INSTANCE = new NullDataSink();
+
+        @Override
+        public void consume(byte[] buf, int offset, int length) {}
+
+        @Override
+        public void consume(ByteBuffer buf) {}
+    }
+}
diff --git a/src/test/java/com/android/apksig/util/InMemoryDataSinkDataSourceTest.java b/src/test/java/com/android/apksig/util/InMemoryDataSinkDataSourceTest.java
new file mode 100644
index 0000000..33243e9
--- /dev/null
+++ b/src/test/java/com/android/apksig/util/InMemoryDataSinkDataSourceTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.util;
+
+import java.io.IOException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for the {@link DataSource} returned by {@link DataSinks#newInMemoryDataSink()}.
+ */
+@RunWith(JUnit4.class)
+public class InMemoryDataSinkDataSourceTest extends DataSourceTestBase {
+    @Override
+    protected CloseableWithDataSource createDataSource(byte[] contents) throws IOException {
+        ReadableDataSink sink = DataSinks.newInMemoryDataSink();
+        sink.consume(contents, 0, contents.length);
+        return CloseableWithDataSource.of(sink);
+    }
+}
diff --git a/src/test/java/com/android/apksig/util/InMemoryDataSinkTest.java b/src/test/java/com/android/apksig/util/InMemoryDataSinkTest.java
new file mode 100644
index 0000000..f48510a
--- /dev/null
+++ b/src/test/java/com/android/apksig/util/InMemoryDataSinkTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apksig.util;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for the {@link DataSink} returned by {@link DataSinks#newInMemoryDataSink()}.
+ */
+@RunWith(JUnit4.class)
+public class InMemoryDataSinkTest extends DataSinkTestBase<ReadableDataSink> {
+
+    @Override
+    protected CloseableWithDataSink<ReadableDataSink> createDataSink() {
+        return CloseableWithDataSink.of(DataSinks.newInMemoryDataSink());
+    }
+
+    @Override
+    protected ByteBuffer getContents(ReadableDataSink dataSink) throws IOException {
+        if (dataSink.size() > Integer.MAX_VALUE) {
+            throw new IOException("Too much data: " + dataSink.size());
+        }
+        return dataSink.getByteBuffer(0, (int) dataSink.size());
+    }
+
+}
diff --git a/src/test/resources/com/android/apksig/dsa-1024.pk8 b/src/test/resources/com/android/apksig/dsa-1024.pk8
new file mode 100644
index 0000000..d500d70
--- /dev/null
+++ b/src/test/resources/com/android/apksig/dsa-1024.pk8
Binary files differ
diff --git a/src/test/resources/com/android/apksig/dsa-1024.x509.pem b/src/test/resources/com/android/apksig/dsa-1024.x509.pem
new file mode 100644
index 0000000..e230eb0
--- /dev/null
+++ b/src/test/resources/com/android/apksig/dsa-1024.x509.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICsTCCAnGgAwIBAgIJAP6EmkoBF8UoMAkGByqGSM44BAMwEzERMA8GA1UEAwwI
+ZHNhLTEwMjQwHhcNMTYwMzMxMTUyNzEwWhcNNDMwODE3MTUyNzEwWjATMREwDwYD
+VQQDDAhkc2EtMTAyNDCCAbYwggErBgcqhkjOOAQBMIIBHgKBgQCTDpv8gS5Y+Ehl
+oln6WT/MYBnywrc3tWDfnlY+9MpVDdB2+kcB7WrhobW1L+6ayKmlkrTaAFjiMPDf
+bdyA6hy3fDu1teLCb89R0uodfZa3MDXXlmqvBk4Fdw8fYijWI/q175e4Y5sNYO9+
+QZg8bBIZnxxCdbKASJ6NAHc50ts3vwIVAIebRw3HnYOZbo6rPoBmcBOxcZTLAoGA
+ch+0D7JrbqmR1w5S3VBtTnONLiBYnaz1Ri3Pfiw5FHKfJcfFcQopIOLJwfdBmY4b
+FLGV5u7DXeJNp16Nvl4MrsmVjkWs9MZVAp5RqzrN9JhVi4ShpdelyFjdWOXHPbc7
+NNqQpTjdkK23r/tCE6XkvkCiWm7Rt22LMpZA4ePALIoDgYQAAoGAc8SkppDzSUPH
+SpKrhrldRyh5m4wSH14ZE96mlSze9tRoSDo8hsA9/vGLgoN7F+3jYSvj8m42tmNt
+jZJWk7vPkJHC/9qoEGbVBY+aTNYwVJyKDJ07vZB9bLxpjD/yyQlsn7/vZTOS657c
+W2S817RgGGyGcCNRoKNig6i0k9fzE8ajUDBOMB0GA1UdDgQWBBSPwzoIjftVH2ke
+EJXtLq+bB50lzDAfBgNVHSMEGDAWgBSPwzoIjftVH2keEJXtLq+bB50lzDAMBgNV
+HRMEBTADAQH/MAkGByqGSM44BAMDLwAwLAIUH1GQcpqx8/9p9QfhCRMvcxrECM4C
+FH8ZULK91BMaHodbRMUtdxB9kIbL
+-----END CERTIFICATE-----
diff --git a/src/test/resources/com/android/apksig/dsa-2048.pk8 b/src/test/resources/com/android/apksig/dsa-2048.pk8
new file mode 100644
index 0000000..cf63594
--- /dev/null
+++ b/src/test/resources/com/android/apksig/dsa-2048.pk8
Binary files differ
diff --git a/src/test/resources/com/android/apksig/dsa-2048.x509.pem b/src/test/resources/com/android/apksig/dsa-2048.x509.pem
new file mode 100644
index 0000000..705db39
--- /dev/null
+++ b/src/test/resources/com/android/apksig/dsa-2048.x509.pem
@@ -0,0 +1,26 @@
+-----BEGIN CERTIFICATE-----
+MIIEWzCCBAKgAwIBAgIJAK4uYtTAat4+MAkGByqGSM44BAMwEzERMA8GA1UEAwwI
+ZHNhLTIwNDgwHhcNMTYwMzMxMTgzMDAxWhcNNDMwODE3MTgzMDAxWjATMREwDwYD
+VQQDDAhkc2EtMjA0ODCCA0cwggI5BgcqhkjOOAQBMIICLAKCAQEAv2/yUkci8fnZ
+ppepy1kcOTIrbPJTW46Z2jKOWjHCTd1aqRFunz3gllpx/YDgjboNIYZf2jKhk2gi
+MgF+8OT0hEHQGXSofnqRoVljexA2nC3tPhVyjN14pQ7ZJXp/raHh1uCT5K7Lfafc
+8y5Cs180CdfisspANfCp10jFu1D2hKOLfggUA9efN05c0f2yOx/Pm9uEiz6ftLUJ
+UWgcJenx1WUk4Nb1qMOWgg9nKQyDiBxZKoVCH5/XJO0K225w6F4scol2hMdTRlA+
+9TGb5tvHKT+Y+lE7fBbcBSKzLT3pwD/T08ErpsIoHqRPamLGHeuamGzvXLHwI1UN
+cJNGg2nnCwIhAJz9K+Qpgg7sbqRMeRvIf2cuWRWbi4XnYR+IAGqgfYUZAoIBADks
+N3taHUnIvkcxTuEUiCSb+d1KMlvbZiH5zLVIyzi5kZNQW8L/gR23z8T6qngXXnFw
+tcTRcb0TDqR7iYPxsX3/x+jBjiApIgebqpnPdpiEAUfORZX7w6CpCNsp9Kqxb98z
+4PtKE9z/20Q3zKhqlwDm8uSjjT7E5xw/YpV8bg7YuQ5eQ8mkonipNXllf3qmDV48
+see/+QwIeBiL7jTjgeBv5ziL8BY2AYmfOwGKwmK9ERxJpIoxgS+5fFuA2oAQ8CwJ
+UDwwv5Wbz7NVkvljHK1Fh5u26ZHdeLT4qp4F0GamJcikq4MqFco6ToCvp8NoDNMZ
+MOno2k1IvsIwrIbrQJgDggEGAAKCAQEAuxbFbx2H4n4BqfbkC9tjn/Mg3zr4LZgG
+7v3FWpzpkbAcEcFCVIqTmJiRlsuf4ml/t9hflOvarfD6TesSc7gyGCJ/2QiqJcI+
+Vif5AKqZskQFlZ5BUMIMjPFMy1WtTVpEotmdbIOaQif4wQrz6SNFUOAXPBKRTY33
+HOLMoo8FRiZ1+uMu9PlUWYqMhSJg+rm2AQPt06D+JToXREaNkYjN0K97T2MTcUNh
+OWiliH/zFuF8N2s6IlNaCv1Yc4FoYEIRoS07dUxcjrV9KRd0TyU0q++rRPluytJP
+yAoyTIrfwa2SM5JR9RtdBZsPdR9Ckpy4ZKSJqDzTbCIU70zsGgHA4aNQME4wHQYD
+VR0OBBYEFL3koBjVHAySf8Xr196yqDT6VhwSMB8GA1UdIwQYMBaAFL3koBjVHAyS
+f8Xr196yqDT6VhwSMAwGA1UdEwQFMAMBAf8wCQYHKoZIzjgEAwNIADBFAiEAjowh
+laeXA/CUrHt6iH4u6edWGeZzyFGlpWsxssKTMBECIG2tZs/xnZVAtXioiIcH1CXT
+LN5AAzZ8wlNUKSvTc12j
+-----END CERTIFICATE-----
diff --git a/src/test/resources/com/android/apksig/dsa-3072.pk8 b/src/test/resources/com/android/apksig/dsa-3072.pk8
new file mode 100644
index 0000000..f0f8983
--- /dev/null
+++ b/src/test/resources/com/android/apksig/dsa-3072.pk8
Binary files differ
diff --git a/src/test/resources/com/android/apksig/dsa-3072.x509.pem b/src/test/resources/com/android/apksig/dsa-3072.x509.pem
new file mode 100644
index 0000000..0284b13
--- /dev/null
+++ b/src/test/resources/com/android/apksig/dsa-3072.x509.pem
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF3DCCBYKgAwIBAgIJAKmpH5wXPDbGMAkGByqGSM44BAMwEzERMA8GA1UEAwwI
+ZHNhLTMwNzIwHhcNMTYwMzMxMTgzMjU2WhcNNDMwODE3MTgzMjU2WjATMREwDwYD
+VQQDDAhkc2EtMzA3MjCCBMcwggM5BgcqhkjOOAQBMIIDLAKCAYEAscwotTL9jjwZ
+CpGRqXVezU9P0zY/wHFYMQB09JjgoCJRm0D9FZzmTnySJa4suwlwoduzgv+pkIkk
+nn0w+hL6LsblRTEuFj11o/fLLAZE4KmdIAPkCcH74HOnws4BwazJLWFtVEv+bdyj
+CmrI2urYWT5uRJwWZmkG4iUnoL/5FUv2pvpSOzu6EO+qFcFcWf3/VrQDaaP3jRRh
+iVCThxvX5xOrO24krrOFlHQufY0A6fHYQqII6EYac6tKTI+ltz121DYH914SHMe8
+0JJYpDVOatjTJADyg1AzHAKsyfTKHHpHbqkgU/CRtALEC1DUyLqQhHvBGpodmqPH
+r9Mbd7pXudw2h6O8uapAsmaFt/YdrSbd37UvhLJyyW0fodTjBTA0WiNs0CLTJL9o
+422Nt1cUDQS1JpsghAzmMWaavq7+QIRhpnMHL47p1jCZvcwMboMlcJwOCjyyZS0F
+fmaD+pEi56Lx+9XDblESM78crb8/lbvuohyrwIJciQmL0hQoKIg1AiEApYT1sROj
+BMB4FIQaS86VKdJ4h9OR13AraES3ba9WV1cCggGAI9LskZ7wcUna6nZyGT4f3bH1
+KSeD3602Yd86NKuB8QGr/QT5L+wFwCuvvI4V8P2lM4wb+q1LrR688f53WS4c0Kin
+/MeN4YtkQBzV4l04Bxrce//EcfQzUPDAr41Fwu2tA0BHE67trxGjNS7SzoFZUiDo
+d/aOZCVilFIay5oF2X8yeNcpTynsxTbGxjxhhgG2tS0fvxhbGF6rl4D0Ddskx6L/
+xjstzu0ADE8FNpDehuKqxzOisNLIJgc1INPQ06AmaUF1Pge74T2AJYPYwqfzArG3
+pj+l2iND0PM7lDeQCgivPAqhYrveoIqJe0NAEu/PiCnOW50hQ4F6dl9lzn9sGXTP
+5KxdR5OyG+Ki+nWTN8h3yOy9ZK7Gh8gUeqE3QVow4+1LhcO19C+X+NM588KbKsq9
+KRP5H4v238g6oF2wgbmPrYvqiwoa1qjHvUX7ZjQuIfrN9+lICflsHKtAtchuUIiO
+b1AWPeaJwAynTn2EeDsVd0e8bm+h+g7ZSDAwUOKuA4IBhgACggGBAJfW6T1BxtZo
+EacqR2TwTi9B3O93Zcj0OzDOn9UMahepcfaF1B3wq9UdJ4PPIC1U27CaEXEJNxgJ
+cIURfFPDXA6pLVEqrGgRX2u0FR1pmHVIxjBpvPJvO954+Hawp+ClU6PKLmxuVZpA
+VeJBx7C8/DU/58J//iuG9B6/mwzPuEoPmnGYMsfVCcEX8yQ2PZXyNGp+KnUygyql
+mMxpE8UXtH6mHYontjiw4Afxqwop3v/bG8eT5FHhpXoQDSN/oeGghDaq9DyIjCqr
+GXZDBvEufLYRgOvnpV6T0oMa9U2W2Trz3HYD6eX09FOhGnMWl9euYrSVRihAKBh1
+72ystvPy2R4wdAA5EQf7EsDdgt/QOMf/AvkaKHMP7DNK5BZxwPQ0KwRkLB+0vaDI
+Mnpu29L6TiixMxI1ihnZbR/U0v8H+/SlSjZaPNRQeuoV9d8t81miUqVSMLcpqNui
+ZljSCtoGfktPAbPyFwGenBkGK5oo0I381KTECMynC0R2P9CQUHegeaNQME4wHQYD
+VR0OBBYEFPRqMyQLPdq0guTx3YKiVxtWN+bPMB8GA1UdIwQYMBaAFPRqMyQLPdq0
+guTx3YKiVxtWN+bPMAwGA1UdEwQFMAMBAf8wCQYHKoZIzjgEAwNJADBGAiEAjcit
+8dFR02elWKoeRAounP9TE2aqDqd5cJXqXn0ssMYCIQCIINjXEYRovfVjDotKelRg
+5k0lmzMmx6Xfz8EgZDLovw==
+-----END CERTIFICATE-----
diff --git a/src/test/resources/com/android/apksig/ec-p256.pk8 b/src/test/resources/com/android/apksig/ec-p256.pk8
new file mode 100644
index 0000000..f781c30
--- /dev/null
+++ b/src/test/resources/com/android/apksig/ec-p256.pk8
Binary files differ
diff --git a/src/test/resources/com/android/apksig/ec-p256.x509.pem b/src/test/resources/com/android/apksig/ec-p256.x509.pem
new file mode 100644
index 0000000..06adcfe
--- /dev/null
+++ b/src/test/resources/com/android/apksig/ec-p256.x509.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBbDCCARGgAwIBAgIJAMoPtk37ZudyMAoGCCqGSM49BAMCMBIxEDAOBgNVBAMM
+B2VjLXAyNTYwHhcNMTYwMzMxMTQ1ODA2WhcNNDMwODE3MTQ1ODA2WjASMRAwDgYD
+VQQDDAdlYy1wMjU2MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpl8RPSLLSROQ
+gwesMe4roOkTi3hfrGU20U6izpDStL/hlLUM3I4Wn1SnOpke8Pp2MpglvgeMx4J0
+BwPaRLTX66NQME4wHQYDVR0OBBYEFNQTNWi5WzAVizIgceqMQ/9bBczIMB8GA1Ud
+IwQYMBaAFNQTNWi5WzAVizIgceqMQ/9bBczIMAwGA1UdEwQFMAMBAf8wCgYIKoZI
+zj0EAwIDSQAwRgIhAPUEoIZsrvAp9BcULFy3E1THn/zR1kBhjfyk8Z4W23jWAiEA
++O6kgpeZwGytCMbT0tLsBeBXQVTnR+oP27gELLZVqt0=
+-----END CERTIFICATE-----
diff --git a/src/test/resources/com/android/apksig/ec-p384.pk8 b/src/test/resources/com/android/apksig/ec-p384.pk8
new file mode 100644
index 0000000..f22507d
--- /dev/null
+++ b/src/test/resources/com/android/apksig/ec-p384.pk8
Binary files differ
diff --git a/src/test/resources/com/android/apksig/ec-p384.x509.pem b/src/test/resources/com/android/apksig/ec-p384.x509.pem
new file mode 100644
index 0000000..9d1403b
--- /dev/null
+++ b/src/test/resources/com/android/apksig/ec-p384.x509.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE-----
+MIIBqTCCAS6gAwIBAgIJAMRWS+CLIsxqMAoGCCqGSM49BAMDMBIxEDAOBgNVBAMM
+B2VjLXAzODQwHhcNMTYwMzMxMTUzMDU3WhcNNDMwODE3MTUzMDU3WjASMRAwDgYD
+VQQDDAdlYy1wMzg0MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE18hjdbk/HXGQNIuF
+xKPMAZO3PQnROO6izB/mHM1BKPpih2/51iMTFKn6KCU9NZt/Q4Z+PpZVLuawEWP/
+uoWwWIj+60vk25z47/Sr0icelSDGt9T9ujiNP6aTA5hc9gypo1AwTjAdBgNVHQ4E
+FgQU981MoejFjh0rbaGXODywOYvB32kwHwYDVR0jBBgwFoAU981MoejFjh0rbaGX
+ODywOYvB32kwDAYDVR0TBAUwAwEB/zAKBggqhkjOPQQDAwNpADBmAjEA/58rXa+F
+mB6JwB89/IAucpNlktjSPrH2tD63BSROvpUpXNy+p+OlJu4sCvY7HnwEAjEA0VWw
+QqUBFLQHFJx1JjMYYfT78V8ylY+Ns1lxrdvs29NNg45MA9uw/ZVMMHgTFNph
+-----END CERTIFICATE-----
diff --git a/src/test/resources/com/android/apksig/ec-p521.pk8 b/src/test/resources/com/android/apksig/ec-p521.pk8
new file mode 100644
index 0000000..9c75e4c
--- /dev/null
+++ b/src/test/resources/com/android/apksig/ec-p521.pk8
Binary files differ
diff --git a/src/test/resources/com/android/apksig/ec-p521.x509.pem b/src/test/resources/com/android/apksig/ec-p521.x509.pem
new file mode 100644
index 0000000..a9b8194
--- /dev/null
+++ b/src/test/resources/com/android/apksig/ec-p521.x509.pem
@@ -0,0 +1,13 @@
+-----BEGIN CERTIFICATE-----
+MIIB8zCCAVSgAwIBAgIJAOxXdFsvm3YiMAoGCCqGSM49BAMEMBIxEDAOBgNVBAMM
+B2VjLXA1MjEwHhcNMTYwMzMxMTUzMTIyWhcNNDMwODE3MTUzMTIyWjASMRAwDgYD
+VQQDDAdlYy1wNTIxMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAYX95sSjPEQqg
+yLD04tNUyq9y/w8seblOpfqa/Amx6H4GFdrjGXX0YTfXKr9GhAyIyQSnNrIg0zDl
+WQUbBPRW4CYBLFOg1pUn1NBhKFD4NtO1KWvYtNOYDegFjRCPB0p+fEXDbq8QFDYv
+lh+NZUJ16+ih8XNIf1C29xuLEqN6oKOnAvajUDBOMB0GA1UdDgQWBBT/Ra3kz60g
+Q7tYk3byZckcLabt8TAfBgNVHSMEGDAWgBT/Ra3kz60gQ7tYk3byZckcLabt8TAM
+BgNVHRMEBTADAQH/MAoGCCqGSM49BAMEA4GMADCBiAJCAP39hYLsWk2H84oEw+HJ
+qGGjexhqeD3vSO1mWhopripE/81oy3yV10puYoJe11xDSfcDj2VfNCHazuXO3kSx
+GA/1AkIBLUJxp/WYbYzhBGKr6lcxczKI/wuMfkZ6vL+0EMJVA/2uEoeqvnl7Bsdk
+icyaOBNEADijuVdaPPIWzKClt9OaVxE=
+-----END CERTIFICATE-----
diff --git a/src/test/resources/com/android/apksig/empty-unsigned.apk b/src/test/resources/com/android/apksig/empty-unsigned.apk
new file mode 100644
index 0000000..15cb0ec
--- /dev/null
+++ b/src/test/resources/com/android/apksig/empty-unsigned.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-aligned-in.apk b/src/test/resources/com/android/apksig/golden-aligned-in.apk
new file mode 100644
index 0000000..97f7881
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-aligned-in.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/golden-aligned-out.apk b/src/test/resources/com/android/apksig/golden-aligned-out.apk
new file mode 100644
index 0000000..a888228
--- /dev/null
+++ 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
new file mode 100644
index 0000000..8939a9f
--- /dev/null
+++ 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
new file mode 100644
index 0000000..a888228
--- /dev/null
+++ 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-v2-out.apk b/src/test/resources/com/android/apksig/golden-aligned-v2-out.apk
new file mode 100644
index 0000000..3794463
--- /dev/null
+++ 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-legacy-aligned-in.apk b/src/test/resources/com/android/apksig/golden-legacy-aligned-in.apk
new file mode 100644
index 0000000..df0e6c1
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-legacy-aligned-in.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
new file mode 100644
index 0000000..a8520e6
--- /dev/null
+++ 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
new file mode 100644
index 0000000..20fe0fa
--- /dev/null
+++ 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
new file mode 100644
index 0000000..a8520e6
--- /dev/null
+++ 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-v2-out.apk b/src/test/resources/com/android/apksig/golden-legacy-aligned-v2-out.apk
new file mode 100644
index 0000000..87c344d
--- /dev/null
+++ 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-rsa-minSdkVersion-1-out.apk b/src/test/resources/com/android/apksig/golden-rsa-minSdkVersion-1-out.apk
new file mode 100644
index 0000000..0ac5356
--- /dev/null
+++ 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
new file mode 100644
index 0000000..bfe89a2
--- /dev/null
+++ 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
new file mode 100644
index 0000000..bfe89a2
--- /dev/null
+++ 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
new file mode 100644
index 0000000..bfe89a2
--- /dev/null
+++ 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-in.apk b/src/test/resources/com/android/apksig/golden-unaligned-in.apk
new file mode 100644
index 0000000..a52e30b
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-unaligned-in.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
new file mode 100644
index 0000000..44584f1
--- /dev/null
+++ 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
new file mode 100644
index 0000000..8fe9740
--- /dev/null
+++ 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
new file mode 100644
index 0000000..44584f1
--- /dev/null
+++ 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-v2-out.apk b/src/test/resources/com/android/apksig/golden-unaligned-v2-out.apk
new file mode 100644
index 0000000..199d3ed
--- /dev/null
+++ b/src/test/resources/com/android/apksig/golden-unaligned-v2-out.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/mismatched-compression-method.apk b/src/test/resources/com/android/apksig/mismatched-compression-method.apk
new file mode 100644
index 0000000..9fe9ff9
--- /dev/null
+++ b/src/test/resources/com/android/apksig/mismatched-compression-method.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/original.apk b/src/test/resources/com/android/apksig/original.apk
new file mode 100644
index 0000000..05f89aa
--- /dev/null
+++ b/src/test/resources/com/android/apksig/original.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/rsa-1024.pk8 b/src/test/resources/com/android/apksig/rsa-1024.pk8
new file mode 100644
index 0000000..7b4beee
--- /dev/null
+++ b/src/test/resources/com/android/apksig/rsa-1024.pk8
Binary files differ
diff --git a/src/test/resources/com/android/apksig/rsa-1024.x509.pem b/src/test/resources/com/android/apksig/rsa-1024.x509.pem
new file mode 100644
index 0000000..9498ec5
--- /dev/null
+++ b/src/test/resources/com/android/apksig/rsa-1024.x509.pem
@@ -0,0 +1,13 @@
+-----BEGIN CERTIFICATE-----
+MIIB9DCCAV2gAwIBAgIJAP0KtYjhFu3IMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNV
+BAMMCHJzYS0xMDI0MB4XDTE2MDMzMTE2MTQ0M1oXDTQzMDgxNzE2MTQ0M1owEzER
+MA8GA1UEAwwIcnNhLTEwMjQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOOP
+r4Y+uCAihcVjey8JmdjyfhZXplVf3HnOQfvWcY97nKnJ7L977QiWajIn7iZRAdVX
+PamVrEbaU6uklgcJFG/qirtscOf6fMBf6GaP2PAhQG89MQnUt9rAjxUAakzWOBTz
+bH0gHRDEGQ30LCA1oSlbLldHz+zBKSC7nsSKYp+9AgMBAAGjUDBOMB0GA1UdDgQW
+BBT1x0TcWRB4i9JnU5pvRtrEv+95OTAfBgNVHSMEGDAWgBT1x0TcWRB4i9JnU5pv
+RtrEv+95OTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GBAKs024CvDYFv
+rj3GTXM8A9Jslg/86WukzEl/+PXDjbPljNV24RSsFVW5gO0ps6Q/EBkbllJP7xNO
+XmOUDyqUcvcwC1zzySqs8kJcF5GCuRXajy4KiEiA5VRmVUSShhnkYX7g1yXkhWrP
+Ps1fQArqHx84+naFSh9kVqu54QIykS7z
+-----END CERTIFICATE-----
diff --git a/src/test/resources/com/android/apksig/rsa-16384.pk8 b/src/test/resources/com/android/apksig/rsa-16384.pk8
new file mode 100644
index 0000000..c8d2793
--- /dev/null
+++ b/src/test/resources/com/android/apksig/rsa-16384.pk8
Binary files differ
diff --git a/src/test/resources/com/android/apksig/rsa-16384.x509.pem b/src/test/resources/com/android/apksig/rsa-16384.x509.pem
new file mode 100644
index 0000000..22b6148
--- /dev/null
+++ b/src/test/resources/com/android/apksig/rsa-16384.x509.pem
@@ -0,0 +1,93 @@
+-----BEGIN CERTIFICATE-----
+MIIQ+zCCCOOgAwIBAgIJAOd3bpikuRKvMA0GCSqGSIb3DQEBDQUAMBQxEjAQBgNV
+BAMMCXJzYS0xNjM4NDAeFw0xNjA0MDQxOTM0MzFaFw00MzA4MjExOTM0MzFaMBQx
+EjAQBgNVBAMMCXJzYS0xNjM4NDCCCCIwDQYJKoZIhvcNAQEBBQADgggPADCCCAoC
+gggBALDYs7aIK08pNr9fdrpLmUVfSc+3n/RNU2e7o3fzwQf2IMExk/ZmxF/ORgAd
+pXPEkn7uOLYSp+fJHUgqsca+8HsxTN1ypKov0X3OeFbnBLXmsoXgjRzaMEgluupd
+B81xnAKE/Vb+HYaEJ4YP+3UgBLIItnwLtL6vdh+e4qQGF9KEAv+T+PdrNQF0EGWK
+weCaOuMKRKfSnNXMkgIgWgoEefsBXNOR/jRJVFxsnJlwsB2iw0FbKjrxnlmwBL+f
+A8YxdMoCLxjL0TuUEmRyd0jO7Td49kUIkW6ux/b4qOvcUPQiODXeayesJ1lmFepS
+ATjhgCz2+C3pcAJOPvOvC4v0+5OQ3YrU2kKsnj8G2ic3SdPUq4UUvQWqY+rCZVDH
+IqsEs/+XO++wMELkN3uiQcRIEO8aCug+VxMSRiyRSSNtxmcmedUZevbZ6+2leBtn
+AbLP33E5hC80A7WKuddQgOiA1yTUVxatZEaxHFVZlg2nzSYHipvwMVC2+zQ4yrKA
+mbNp5wiVUV5WLsUkYEuSwl+MG9vEjr2gTX+qLrvpIYS2AF/16SOEw5zQBPUcfVIz
+eJU+W//wwXdlNmA6qRCDXqLDBX1MQrChpaI2eRbv64k5C09LtEA5nt/lJt5dm8IF
+XpurOZqHTVG3CCS15SM8PyoApYKN0oM9+6Z+FDUOB+VbTW/hAqKYLNBJmNf8mRkK
++c1peoOAYw5iG8iaSD8f8bcIi3oFPMdt0Gs5vDoLjbWmK2s9P2wknA4hmVe7hvSy
+DMvyxj8j4BLadc3HafcXYPfoNPhfgoiZVLm6ijEj74iKeLSkef8F3x0Agulnitz3
+CWt7X6EXHbqR/0++VTqgnFDe+enf9oFzMsDDbOs59dpyGFSP159dTqJILimF5xUG
+Fw95hsTbdEblZ7Q2MiibB38ifz8WT+Pix72SnHrlcnKonv8Tkeoie0AP+dYkYXpC
+Dy0oIzl7Vhy1e99RjX8kKjZAfCuiQ7wnOGNu6V32UyKMvWD5E6mNLpsvyBRTxDhs
+ePpH5dZWbhg6WxhD68QG/Wi/8FRmc8/TPpPXilOG3HHtX6Q3yfGYHJB6/dJhWQQy
+iZawyEpcyZKjEyWoJayRsKSLb6gW6Idfc1Uf/yb6IHDRlYZEPF7JuznjLCaz2QC9
+8GCBGfUE9OGH+LtdMnsmn2IYEd1FtWrRGduG0HNKubmx8bJmc1HkYDQg3cksPb2o
+jwCND2ALGtTcR30yllmSmEJKpXBYB00iRvxvkBqCkL4THOXhN2V5uEMrS0r8GRZN
+QCvCoFKulPjITmlQ/ciVonN9y9qVeRbFE4ZxiiBxF41K/Mw45ugIqfg4ndJqbub6
+5p15nhYJIC2CbsVL5+1THmd+kompQhUo1ttwof0aHh3KLwe1uq2OEU7Tz71Ct5+G
+b5JjIAlQDuGcfsx3utJY3AE5ailtvaRNAz3FyTsZGkIOeqX4uswWltxGnuSMSbPi
+CPv7ngFXkytkK+3Oqs92XM8mhO1yqPzmm73+qZYDGFdy32C/Rr0enETmgkUFPToH
+28hMcOsEt+7L0GM6A5N708BaF9Csy7XTFz47Hk2CjaKTvP08Tl3bFPULrgJ5KCr6
+4hc/bVIDkU7c1lhDISJ1yuw4KHizmjuf4UeRBBDoB3MpPwZSeEkggGXuX1wkFApu
+RGhjCXQgFU9bNtUHM6Kj0tPQZrgsvx2wwBUFVOfPoej6afrJqpvep+EN1+OJCE4c
+AzuZ1CdE96vEleVbrq3BQaJbsm8JqHs7lAn1aiCK0semqAoXD4cT4L2H4E37IDlm
+PHsxIeTmgD7/TP3InuFB6sYvVM8moG1TLmtUJkpc8kkPBPNbTRHwuRvMGD1pSsP1
+aNbgKkEfGqL1iOrfoTCRGKDeDDxWGsxDOgW1hRs9wGzJuMQjl1Rlb11lnnX7UYyT
+UnD/yw7YaplScWwpsqntQewI59A4ad1wOJlabDUFwkD4i4ERTcjyea1ydE03qaOy
+0ItJbc8kjsEMWFAv1+y7/cxD7kALcytvBHhD8OVJ40qJrtwRsXnv2T74cTPh87qh
+3j2tjdJKVSLxwqg5ZdvIemnWmmFAPuRERResp5j5WLJcOFcXhbp4CAQLViRoktLf
+AKtgKSVa9FQLvombOq8GqWxqeGJEsq+8/X6UOj/RDPu8gUCdodmlxgC5BpJlMTfg
+9ElvHb4oRewPfc5MBV5i++8xagxS1+5NM6z+1qZfT+XJcJ/wFYyeSvGGdEgaDIVd
+XGqOFDmA20bUl0xWSM5j4At2CmymAn32i8FR5lNfB/f8tIGBlXQyrYzpAKoj6FrV
+3u43zKgYBzDTqNs61e+A8MScV5666gvpcOltFiaucc455JGiN2v7N+SAygWaX3nb
+jidmRaIAftup6kXVXxy1ZwpKCtUTqo0M+S/jO9clHy1EYaSX12blCL1B6OFpMRV7
+1foffUHwaPrMqqbPphgN4QRY2Ao2sKSliiP1T8s1T2iteEGiazCdWSOVVi9Acbo5
+DfeBlWwhW6OQpv1nMTznGscc4+ledP5yG5C6boz2aNwK6H6cpDfEc7tWTWhZN5Jm
+Jlra4pseqf9Lsc/Y4QMD1312C3Hu8EtJ/qlxEDhLnCW8PHxdlRsIkmcV8w+as/hu
+6/MlOYcCp9ae6nooFL7ZuDkdDWm84UXdce2ZVVGVoDa+RdpICnp79y/9CLxWue4H
+09VFEFUH//E6lOCBymUjTSO/DQ6z+ceb2W5B3WVV8gqADTDhAgMBAAGjUDBOMB0G
+A1UdDgQWBBQkHp1wX0Sfr0xz7xcip9UwEIFTLDAfBgNVHSMEGDAWgBQkHp1wX0Sf
+r0xz7xcip9UwEIFTLDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IIAQAd
+FhlHg4E7Yp8kIOfZRU5Cma6wbOSd2eHkV28WHGdwpKsvNhzgQEj+scYWSS8geozi
+vqSdJCoMmY8hWJh4SY0ED1DjPMoRvE8OotyGoCJovvYQia+gbVneT8JnfV3fkdwi
+hpUmAhokrsHkBj0jp2Ubff/D5yflA+QPCmhZZnkow+5QHXtmpy8CL9Fzfonz5uq7
+yCV5uWRicczFbQw3pDSXKn5OFqXuC8H/8R6Caq2TkJ1LusVtZJevcHBkEQ6e+XEX
+kZ+QCNtHw0a67LQEPtMXSyqZ/zR0roqwT6udUgHhdvZjbbb9GDpTW3u472IY18E+
+bf+npZl9kyuv2kyK1d2IjL45TxjBr7vLbjsP2UsmZvb3Wfb/kiMscvTBcxOL7/WA
+GNJ0XifmJWiDTDC+gUBC7LRN9lG8F7ykrMTtMUNlhow5LpIi0HA8TH58yq3/ulGq
+XWpculy33kcVAFTMGh57r8zq9DwkeW+RWggvHO2422S1bFGmKpizKASTvY6iqo4H
+yKvE9uZ41WaEbpp9WKPIaeup+ynxFpcgwMCKwvs7Yaj+mVexv7CoJ9nrEhMOHDxV
+GRyWDdBoIi08S02JHztZBXp5NZ+hqey5HrO4dhrnV1nVYJmH89KcAlXMfTZ2YHsv
+R2+dm6K8ToaX+Irqbz7Xbv8WG/aAdUqMSWkFEss7OT7VZBUiAdFaWqg0D0wtWSSE
+jZJJs9ISUTClq+97o9BEH2sAebchLFP56nY+Bj/zHBq2qPxTKdKE5BH13KcK1fwO
+eNn8a3SlSEHraa0oV6VjgSoMNdFz7b7b0r/Z8L4PEASJgH+VaGRm2TtuVWFHSqvX
+015UGITk6YgAQ25MTprJc/oAd0dut+aCPtOVElfukYvdrbw1YYQ9tc7kU+AVrzaf
+ytWj2GYR5Slfhle0inKlBvbpLTAHs82bp2Dgy9ZSQzW9/gIvLvCt+Hj0kiSYNcbX
+W7Ai5z2i6XKE7DdQO0Uzt+1bXGK3j2PI+81lCw2ejCFwjdYDWQw9f2nzQ5FgeYwG
+tc6fS4GbJM97n0yH9rj0Jb25AummZGnEL11ytPpC6Nv9cQdCuKDbaWQuQyRMCLEm
+hHaqV6k/fI4Etvuo15pyfJ9w7Xrhc6emgdg2HzJ99lDGkzZAF/3FR7N5pLPk5E0/
+PPlXUSiEx17IeWp3rNt0YSMixkGz+EbKyv9RIZzm/LV4zAzs2ZyUHHavUgZ602eg
+89ppqafaBrCwIWB1jUmnHJop9YlXQ3hE7pAV5qf9GxZLwUdzJcyLte1/vkn1yt94
+nLOZPPWUwUjIaBOZ7e/g8fHBjvAYwyoy3toKVpvkhR48NvcYD8pQ9cB6rIL0JkWV
+nQEYeISlJCUOO3K2eZ3ZH02ftha5gLshcGRXy9NS+4fNxDT3H+102RqSxmKPIxV0
+onV9RyOxUPKLRGjCZBZxs5aSxTYjFJ591azt3yAY4vwCnnHqdNGFbTat/Zc8LUOO
+J26n5cOYFGKPvZVvj8jMNYC1wo+R+A+1FeYXBV4MSVxCB7tjBlbU6OIyZSWLZ8Vw
+LMcPbuZ7ESj1LeTONwS1vspZM8Y/M8+RXv9VA8Z998tnNopdU3izVC2z6Zn9hNNI
+XBDbSe6ZRwsjXrm5TZCBgA4ZE18MwxPVhntSvl87Gc3wF4hz4BOiZXmffrXK4nQJ
+aVA8E2IsriCV+GQBN/ui+w3U9LbtsHrbauhjrru5EfYohpuInwvgPlAnTztdv9u7
+ee8RhwaCa+MInZanD6pRAAcfM6O64CPxHZtfVW6JM42N7wQXijvYzJPVR30F+6o1
+C+KwySuMBOGxOctmzLj938/OMrxuLBOmv3PJvSnHV0pWtbR7r7jH3v0uUm54zH56
+Qo/Rm/Aqf+m4Si3Xtsf9zvc89sqG5v2TT882Joja76zlJfaS31QgbnLTmKtAHtIC
+mqfQPvt1LNEJIiB6FZDHJIW5Ccm7imsixerxCBBoAt/J/dhW6N+cjZ0EWG5NiSoz
+9LmAZv8iWyqK/KvdPXUopQWqkYUvuIyNCYqzTRLKudUMohefNwghvl1gSGp4IMZ/
+4LyrJHi9eCcD9Z65PJsRTua+742N2sdhFfU/C4atOUGSK9x/Dl79Qkgsl6HqAoce
+HXiHAIvoOqC+jzEkjjxow30BzJeGsZoFwNvMUW7HcQ523DiIOx6MX8oQyKEo+W6C
+ayFvvvT3qHu2hL2ZxOXE+rGyUJnmwqctz4ChLvyYXa/eNrycs382x2U5XNXgXzNT
+3bwB9B+LnKSMJEB+UvHdbBcafYyevLptbF5xiiiUA0P3fq61AfmNiCzJWb+kaO11
+oHHQNWyG/fO49u3bZJkhvlsk8GXAp9uTqdW7YAqxjy8NohFewmtpTJPE62XKIqiq
++dqo4nUT761iaUBxgyj1v5jKcXT2JiEMnEe4AN7pZJ01pCNXQrXl+6ru4TVV3tpy
+OsDJ9UfZo8xZXEAJ/gvSyiih0xq6xhwGuUyExC3GldBz2frveWImxVEiqQIdHULH
+WwB6eAm9T1f+2hOGq7AB9Jb8CRyQniJWXtWu9uJBt+XwSt5lN6VUjeLt95SitvjO
+llqs0zhvTf52H8siwaO83Cui78iamqv7jVatB3JYW71S5cOyZ/x5Z5FYqKi8/wjO
+L4OyUs54kfcJllsxAmS014UgcTrJpbMNw7jSzLX6FxT4MEbyARK8wWQfEZQ2tCeo
+IOzfcYvlY05mG0KSzs6ZGBrWRZQDPcbJ0CKNSLTFbQ==
+-----END CERTIFICATE-----
diff --git a/src/test/resources/com/android/apksig/rsa-2048.pk8 b/src/test/resources/com/android/apksig/rsa-2048.pk8
new file mode 100644
index 0000000..105f7e7
--- /dev/null
+++ b/src/test/resources/com/android/apksig/rsa-2048.pk8
Binary files differ
diff --git a/src/test/resources/com/android/apksig/rsa-2048.x509.pem b/src/test/resources/com/android/apksig/rsa-2048.x509.pem
new file mode 100644
index 0000000..0e7b38e
--- /dev/null
+++ b/src/test/resources/com/android/apksig/rsa-2048.x509.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC+TCCAeGgAwIBAgIJAI41MGzdARX3MA0GCSqGSIb3DQEBCwUAMBMxETAPBgNV
+BAMMCHJzYS0yMDQ4MB4XDTE2MDMzMTE0NTc0OVoXDTQzMDgxNzE0NTc0OVowEzER
+MA8GA1UEAwwIcnNhLTIwNDgwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQDQ4JI1EJ239V4wss0jpVlZMudh2/kARCVdoBgsRQuvc2RNnO23Eyynlt9UN+Dc
+NRdQIhbCpVTjdEl/bePECHlqg9NE3frAj5GebiUdWL6A/idKsZA1nAKyIgxxjcnu
++38OcrlO6XOm36euxGfd/ULrghZGXzMVFq4uLiIv3DqFkUcIlE0BvUiUoNwpopV4
+MKj1GQgoaEObJG5xkMBKO6vg36VfJ3s3V3r48uJxYGhhBZEB0EpoXLd4i0piAB8S
+MLb0Ek6wA/HZ8A2rdnStk1wl/83OM1jO0uB3hyfJpqIijlvNGnrloYyyOIqS0LGH
+nxSJD7goASH2Ef0h4yxbsOvHAgMBAAGjUDBOMB0GA1UdDgQWBBQXAi1zEH84mzkS
+62ohswGGWSwdbzAfBgNVHSMEGDAWgBQXAi1zEH84mzkS62ohswGGWSwdbzAMBgNV
+HRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAB92T5toLkF6dLl65/boH5Qvub
+5wfIk0AD12T3t3kYWQFOH0YDCHNL3SfmrjYM/CwNJAd1KuCL5AZcn0km/n0SFXt5
+8Ps/MBcb0eK1fYezeEehKUyt5IBgDTKeQOel6So8rGuQRrDf/WV8rt6fugkIODFx
+sB3oj4ESaGXbvmvWD6q4a3koq/nV26kALchnAr7/FTNq3HEIQ1BDr9pldVh1gEV/
+ohHKcQP4M22Es7lredzpIcb5K6Ko/UtwsSRtHnoOjwmb+L/FsgAJsekmcJG5TK1X
+ciIsrrNFDCYzf/d9O1PD/V95kB7460qMzrGWZpc3mLe+OnmVMq6c4omOtIKl
+-----END CERTIFICATE-----
diff --git a/src/test/resources/com/android/apksig/rsa-3072.pk8 b/src/test/resources/com/android/apksig/rsa-3072.pk8
new file mode 100644
index 0000000..163e06c
--- /dev/null
+++ b/src/test/resources/com/android/apksig/rsa-3072.pk8
Binary files differ
diff --git a/src/test/resources/com/android/apksig/rsa-3072.x509.pem b/src/test/resources/com/android/apksig/rsa-3072.x509.pem
new file mode 100644
index 0000000..0f3cd14
--- /dev/null
+++ b/src/test/resources/com/android/apksig/rsa-3072.x509.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIID+TCCAmGgAwIBAgIJAN8aZ+YVywdhMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNV
+BAMMCHJzYS0zMDcyMB4XDTE2MDMzMTE5MTQ1OFoXDTQzMDgxNzE5MTQ1OFowEzER
+MA8GA1UEAwwIcnNhLTMwNzIwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIB
+gQCcgu3kzCLVWeUzwO5vhwWFD4rCJVavnVJjA4hz3fg9wsh8wpK0D39a8iyKS714
+5fFF/EsPpoKo6b8l9GVHfrSzJSa0rMcQ7+GHLyfG+8Wt6cnGwb8m6FOMKM8YAlUO
+zVL/WDZXBGvHu/gmZXM0ZbCy1cFBy5/kcGF2zF/fa+a8n4fUiQlbGC6y5iXKewMF
+z5/tFN4BeShBSNxGJVNOut/bLOvpGoPQVsteFlUgiSOPuXeiMeXDDEMEHbY7u7lB
+Fmjs5yAdXaLtnsJD689NdfIDZYHsiBRf7sFNWWNg4gE0gEPhN0QkC2Bkak80t2Mw
+XNakB7gqJ7Vsy+ZPBmhzMzeZbrK9cZXgqNNlxmraHycGzPif4sqmpy1/xFxNqOQh
+n2cc0fd2RG0ql1T4qO0U2b2NQzNMcgrtBW2E1UUUVukVJLCa7RKyt7yq7uUqeCYd
+MxNfvWYBSCzpZMvTI7jq6HcCqQOfW8nkE/z2IBdWvBi3yIdgKSsW0uZsW2/1+KlJ
+fhkCAwEAAaNQME4wHQYDVR0OBBYEFLGsv+jET2b4cWyINayy3qf/U7izMB8GA1Ud
+IwQYMBaAFLGsv+jET2b4cWyINayy3qf/U7izMAwGA1UdEwQFMAMBAf8wDQYJKoZI
+hvcNAQELBQADggGBACLyVVZrgLXL0PUAS2WwZruWj9q7lVnzLMla3u9j/fSLnlPK
+/YcuQh/DqovNkPjFrItww4VfLxkQNsdOccAMHWA2rnWivOYnS1q3gM56e9KN/fEq
+yRc3ltZCPvEoFnFEVtVfmgZJGV0xBLZUFHnPjlWtUYXS8pR89x/Klwy5aaJ9OcGW
+SPaI0I3TyFHCb92Dtfdehtdu8YCDrwTyaYLHvYdNQFDrJ20I8WNsG8RwklfAWNwZ
+IJ5fQxwHiCKxvdaTt2NYUSm4R4kEQCgklrgnaEy2kzDVKp4GjQwVrsa1btK+Srrd
+gtIqB+azx3XziuLi129qJGeZsof8ylN5eYmw0r57d6OPB/xj3roBsB77t4d5jEhl
+Y0BZ93t2HWvXJmuolf1JcauDOcGhxxgdiWdcOWeSABH3cNUiilRfs8HRwSzZwB5E
+RyjvKBdGirUAbLgri+8t9165gaz3wVhP4xkcD4e6LoupWHarAMKunMVGVbCOj7mj
+AUPHJ3Z+HhUKiI/q9g==
+-----END CERTIFICATE-----
diff --git a/src/test/resources/com/android/apksig/rsa-4096.pk8 b/src/test/resources/com/android/apksig/rsa-4096.pk8
new file mode 100644
index 0000000..0f498e5
--- /dev/null
+++ b/src/test/resources/com/android/apksig/rsa-4096.pk8
Binary files differ
diff --git a/src/test/resources/com/android/apksig/rsa-4096.x509.pem b/src/test/resources/com/android/apksig/rsa-4096.x509.pem
new file mode 100644
index 0000000..704dcbd
--- /dev/null
+++ b/src/test/resources/com/android/apksig/rsa-4096.x509.pem
@@ -0,0 +1,29 @@
+-----BEGIN CERTIFICATE-----
+MIIE+TCCAuGgAwIBAgIJAIhxrQmG+dcZMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNV
+BAMMCHJzYS00MDk2MB4XDTE2MDMzMTE1Mjg0NloXDTQzMDgxNzE1Mjg0NlowEzER
+MA8GA1UEAwwIcnNhLTQwOTYwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
+AQDTPhE7i4Bv3BQG1ifMPZfTGA1ZvE+mQWOgQ1q/3AQz23I8pIaoj1wFbYDvnTYG
+UCDnlJqKvwL4mrAnxvqTxm+0g/WFOoBKFoNY5OhgcDfomNEFQajY9bgXaNiwEYDU
+0X+q+2KAnr35MF7vdEaHcN8gFh15wObVXmlerMIOWejnwrM4MOXjGjNXtiB+Dg04
+Fb9eHufZ+s1dKNAojD08mUJkxTC1gjmBKPr8hOAPo9ay5NZk6mGaI1E0U5NbIpDe
+0EhYxRXkDjP7f6BzbhFEupCOzZNMC5CxD8fsmku1CaKBayGpsxktYFxsw1RFVTZj
+3pKYnM1fJBHj/hU+7zy60MZNpdU6i5HVa8FPldwAxv6P2dz0wIl9S3rrqDrd2/1p
+r3rKNrMbVo7wzZ8euVhmjttsRvN8NykCniX6oWxuX7EFQxT/ZM0xk055gMMW7xbr
+oLtPAJ1Lk7R6aTCS+ObrhBgS8+38sIT8rF/CCz2KQ68iUpMmMBz+dkYsDwHPq7vG
+wIy8J7fwzTLqUbyO971+4MD9/9HbqfjRAsjfnSz8HqfyHrSDKeAhgqlr8j8DtQ7X
+B2PIZIEmyN//A13xfZQ63c/KR/yf9nPig4KfHulODfWXBD+2T5kzakTSn4My37FF
+mzG0giMMJ3zGs4QpGZAKFGdwhUq6nAAqEKYo+dptlFhbzQIDAQABo1AwTjAdBgNV
+HQ4EFgQU+f3rdfojsysUqNHmjE4c+0I8YGUwHwYDVR0jBBgwFoAU+f3rdfojsysU
+qNHmjE4c+0I8YGUwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAbOd0
+bKbaeGNsinFSck4niiHDhkeD7GhsneReA73LDJVwAsr5SLAYlmZL/R+1Jw2FGa5m
+82q8412xoVxqAbsz/OrBjoW+5RjAUpHSv0vo3Ny9EpFDpRI1yO8EFU2PhpbcC8Av
+101/oBA8tgKDKgb9H9GrnuqKqxLvrOKDle0TEWAb4X+yyuLSneU6UZTn/g6hhKMw
+pRcsCkwwKa2266auq93NA30Xb1UME5med/5fBKzjL7TvA8BTbr8EdHxjbPmnyI1n
+iVQJnkvvk6rDRcWeOt+lP/pmvaCcVFp9FJHlWmxrus7x0PuH8WxecVl6PeV3MLyR
+HXwGLzlqJWFm753FP3sTV3xNqemo1IhtlHR3jgU8Y7Ixq2Ljjs5izucqM+u2Ioo/
+wbzlhjfRmxqGE3RWvq6Vv5C22Th3hEA7PDUKAh1/cvUwZxOh1Wa9Nedhy0SUXAR8
+nY2GHJEN6QmR1ZAoyABHuPqk1IjduDONo0usGk/iYIFMgfwitc5Tv4gdjjBLburF
+NRU3QkoT1twPueb4Xnvmb/yHmTmqm7MM4OlkmTSpYRci8C1JNChdqIjNSF36Flnm
+DEMqP6petbkZcORD7UtgoB+DCPyBWybtm/GDOW2v3CWo94Q5A1yRDpqyQOTIJChe
+sssLUtp3bfmTSFFMfmsovynUlFpsazeFKjQOkYQ=
+-----END CERTIFICATE-----
diff --git a/src/test/resources/com/android/apksig/rsa-8192.pk8 b/src/test/resources/com/android/apksig/rsa-8192.pk8
new file mode 100644
index 0000000..cc7a7ec
--- /dev/null
+++ b/src/test/resources/com/android/apksig/rsa-8192.pk8
Binary files differ
diff --git a/src/test/resources/com/android/apksig/rsa-8192.x509.pem b/src/test/resources/com/android/apksig/rsa-8192.x509.pem
new file mode 100644
index 0000000..42f1428
--- /dev/null
+++ b/src/test/resources/com/android/apksig/rsa-8192.x509.pem
@@ -0,0 +1,50 @@
+-----BEGIN CERTIFICATE-----
+MIII+TCCBOGgAwIBAgIJAMMCN6DTJaAOMA0GCSqGSIb3DQEBDQUAMBMxETAPBgNV
+BAMMCHJzYS04MTkyMB4XDTE2MDQwNDE5MzI0NloXDTQzMDgyMTE5MzI0NlowEzER
+MA8GA1UEAwwIcnNhLTgxOTIwggQiMA0GCSqGSIb3DQEBAQUAA4IEDwAwggQKAoIE
+AQCt2gaOPSTn1A/WkapaytoE2RGF5nBzlfLocagG+0Bgz0RH3QQq0xBEmQivmbj4
+egyKFQQ9dYrefXRnayLQvHpfeT31QXR3iKOfMnvtxK9UnkXo9S/T+czyj91Ox51w
+HFUyMlXjFs8btxi6Lpkrgl3jmL+e4/WI8khvTrdszrO8rBHBYlD8Y7SAvPcVczYq
+nn4m6Jd0/abR5slHiyGtkTREoHmW/u4r4PfTzanLyzcsWH9/td+sYHcu1Y0XTWTR
+x2qrUmpD+7X5cvXP9rsECU82dtnyPZPIHBqzfLJ8oGELsJxgVZ0u9gW0tlmJQdVo
+Q0QPJSWivnNxPl1czJYbXimbY3Zh5hCzdyNqQewVx6iNsNh008JsXJ5/yvJQgLgZ
+UR2gWZMGzLc4V2wiqxuLzELt5RoXPehAdAiOLQu/3Fw+u4J/d7NEwpKgpdDGyG/U
+9nV6mjvomcgOdWKblaRAeiXauYy/MgW68kIu4O4SoKWRRmOF2p+tch3aDKw9M89O
+88QhEzbzDsioAxeOAqL7th3bSPZ2fX05xtM2vFK71QFM41q+FOL+062Ev/VJyU4g
+kxK643V+Cxso9CZEVtFfkNnVIGOI2h8knkOiLtbMnCx/hcxuZLm/XvYQxOO/7ScL
+rkHLUuKu/dK2ksDn8yBgaHfVlHkMmVoSll6fKB+UXpPX7xClrGQAFpVUji5oZN0s
++EcMQdcMtsb6LxnwC9XS1ELQ5FQGuJFatwY9RSV3cbwI6kNKaZiB5sDDAaAHX9GU
+U3EA2fm0u+4UFOSSmYTfx/jl0V1NMQ1oOAY9QMqzckMo2y9cepLQ3temQ/k4bNqj
+rYB3POdqS9OoT74UKU8gg9rv01PaFlLHhPKwVc+MjY36aVefbfk1KZzBpm2twUUm
+F0kz4pU3BYj0aSrTeKWaTLjdn4ndGMSe3QMQQ5GUchWCcygTyID8Wx8+eDzPIs/U
+sc++7fQRuPawcd11oQ7wTjWIjr7VnsV8hiHSyMupEad8HuBt8CNtDf/m/hD1LsEf
+0dY8nDGSPvV/ih2YqTfhcac65D4JKKbV5x65aEwM7frYwi9QsB7CK2xLMHnatp2a
+TRcA24VEsaBP2+UNSLyxkOeeuzSCksHLPBWkVfcRbKUeD9x4Dtwf2WsUtYjGzOQ7
+ApLBB4XHe+GPr/i+PmJP4IDoU1Mifbjla8rz2ZuebZgPnyV7nSvkG/FUqUYpa3Ut
+LY0nF/2dDRF0NirbWuB4Mkk/KlxCQ3w8EZFY5u6L/ee23WxbnVOEQJBtKh7/I/UV
+2dGqoVJx2UqT0MpjUCFjx+KjWvaEt+FfmjfRRqQfHLkJlYly/YdeUrGPdGgJNr0C
+csnZkAZmKDo4OVv+wVduHxHfAgMBAAGjUDBOMB0GA1UdDgQWBBRdiMpGc1L++yQc
+MtftoeGuLwJ+PzAfBgNVHSMEGDAWgBRdiMpGc1L++yQcMtftoeGuLwJ+PzAMBgNV
+HRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IEAQBo9lUFyV8jI0IpcQLC7hC0ILxx
+6DsDw/YpMd9lRyujKa//OVJOEO3Z+SalfoLslMPCD1eeJBxgx9/xTMxZUrunOMJu
+Ddxc5RypVwdt6NOUQ3pNYU+Qkp73aZzakm6/i1LQDOxGf1PaZooPqXaKMfyb9CQ+
+M5wR9XsLDAyQPlylft3cWsp7DaPsrcp7Sn5u33TtvCDgfMwbKZrzzmCdAHS52p0A
+czMpDgYxhxCuHPwN6XSAxlrWnfpkOsdKneiVr4aDm1fp61PQg+sOKNCNJCMmUB50
+XFj14qLmvjLERNl2vUfDqoj6DxC9OceioZljzFM2d8/DEC1/YUT8AGYZQt0ISLlq
+QW4e4iIxXxVNKwXvJZMCd3z4XX8SvAOXL0+WrOffgkQQpSr2jBXKFe0BpCUnzVkN
+jw/bVJ89jrqfupExJ6kKTLzI+H2u/7LgZUn9/QlvhWR3IL1nEvCCOPHXTreuUp2U
+KauH7WQU+mzF/K+obzYiaPc53dz0C3JcvWgv5cBbhDUsGsziZLPkR3r5TvpTNF3t
+c3Ky84q7CfOKI+hGN/S+BNSv+TdA5uOG+h8GJ6SAwVpimzguhx2/iRg7OiHmhoy4
+UfsCF/QdoF3cEoSMMzorOIM+szZ+XggJMbtWzIv4NAL2TCKJcpB6t0Uxci6JN6lM
+tnbq78QxeOoJ3D3g6ZQoGvCdKwHcMQunS2MK75j9hckX5hR+0xnH++bI+D3qXaul
+zCOAcMoGpM7WgyzAc3mh9aAJFk1J9OYzpFY34nX2cDMc0xtI2s9p2/8X14ir5suK
+R0rqm3BZewDaQVNtrZCm/sch1I0cN4GomvxZ+2xD43vsZm8QEB2fyTvazg6kW2O9
+G0coUmszwI63f7fVx+wzZZxS24N17lIO36hjBq12SKDz9C3cIOZpPYdcaNxDyiwq
++RojbgUnosJji+xZtyK5Bf1kNB+jNoNT5OrWCXCR62Z5CQ+fAdlz4NPdibYnwscF
+SD8OA2I7UIT42KAuDBEMKVXe4SlhqLzOdqXwZ7h/ZxyUlY+zpwLO9vfLVpqZiavl
+bx+RO7lkvxPGEtM4mjXt5dNx2rgML/lXSE63qaqspYLJ806A3GE0918/b+ZDfvzG
+HLbeqIVtAlXwOGFLdifLvsgFybQ5yriR/yTlHXN/T8K+Q6wPo3uNkKNgUDq0FmzF
+N9UDQ+VFAdwaGPfBox3okGPoD6/HdiokuwZm4mlioHjZc513qhDapS3fJcXV6tu9
+OhIPBJ5NXVw3IVXwB+eskd2W5y8QlHZKBNTwzc/PBqL93QA2PxOFispDtevAuyTq
+OhRConYsMhoLf/9NqhEsyKVhwtYhYrUh0Q+hCiAg+YpjKuE2YJoUNMVyF3dY
+-----END CERTIFICATE-----
diff --git a/src/test/resources/com/android/apksig/targetSandboxVersion-2.apk b/src/test/resources/com/android/apksig/targetSandboxVersion-2.apk
new file mode 100644
index 0000000..2d7685a
--- /dev/null
+++ b/src/test/resources/com/android/apksig/targetSandboxVersion-2.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/two-signers-second-signer-v2-broken.apk b/src/test/resources/com/android/apksig/two-signers-second-signer-v2-broken.apk
new file mode 100644
index 0000000..fcf154e
--- /dev/null
+++ b/src/test/resources/com/android/apksig/two-signers-second-signer-v2-broken.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/two-signers.apk b/src/test/resources/com/android/apksig/two-signers.apk
new file mode 100644
index 0000000..f11760f
--- /dev/null
+++ b/src/test/resources/com/android/apksig/two-signers.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/unsigned-targetSandboxVersion-2.apk b/src/test/resources/com/android/apksig/unsigned-targetSandboxVersion-2.apk
new file mode 100644
index 0000000..2c92f1c
--- /dev/null
+++ b/src/test/resources/com/android/apksig/unsigned-targetSandboxVersion-2.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-empty.apk b/src/test/resources/com/android/apksig/v1-only-empty.apk
new file mode 100644
index 0000000..1967272
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-empty.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-max-sized-eocd-comment.apk b/src/test/resources/com/android/apksig/v1-only-max-sized-eocd-comment.apk
new file mode 100644
index 0000000..f7c8db3
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-max-sized-eocd-comment.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-pkcs7-cert-bag-first-cert-not-used.apk b/src/test/resources/com/android/apksig/v1-only-pkcs7-cert-bag-first-cert-not-used.apk
new file mode 100644
index 0000000..0ebd01f
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-pkcs7-cert-bag-first-cert-not-used.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-targetSandboxVersion-2.apk b/src/test/resources/com/android/apksig/v1-only-targetSandboxVersion-2.apk
new file mode 100644
index 0000000..4a00879
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-targetSandboxVersion-2.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-two-signers.apk b/src/test/resources/com/android/apksig/v1-only-two-signers.apk
new file mode 100644
index 0000000..f096a56
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-two-signers.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-cr-in-entry-name.apk b/src/test/resources/com/android/apksig/v1-only-with-cr-in-entry-name.apk
new file mode 100644
index 0000000..84828b0
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-cr-in-entry-name.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.1-1024.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.1-1024.apk
new file mode 100644
index 0000000..02ce027
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.1-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.1-2048.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.1-2048.apk
new file mode 100644
index 0000000..62f8e69
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.1-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.1-3072.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.1-3072.apk
new file mode 100644
index 0000000..e8f1633
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.1-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.3-1024.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.3-1024.apk
new file mode 100644
index 0000000..0f0f016
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.3-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.3-2048.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.3-2048.apk
new file mode 100644
index 0000000..aa30612
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.3-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.3-3072.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.3-3072.apk
new file mode 100644
index 0000000..68edee9
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.3-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha224-1.2.840.10040.4.1-1024.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha224-1.2.840.10040.4.1-1024.apk
new file mode 100644
index 0000000..11d3942
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha224-1.2.840.10040.4.1-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha224-1.2.840.10040.4.1-2048.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha224-1.2.840.10040.4.1-2048.apk
new file mode 100644
index 0000000..be8fd4b
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha224-1.2.840.10040.4.1-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha224-1.2.840.10040.4.1-3072.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha224-1.2.840.10040.4.1-3072.apk
new file mode 100644
index 0000000..2f4db65
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha224-1.2.840.10040.4.1-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha224-2.16.840.1.101.3.4.3.1-1024.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha224-2.16.840.1.101.3.4.3.1-1024.apk
new file mode 100644
index 0000000..938ea7a
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha224-2.16.840.1.101.3.4.3.1-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha224-2.16.840.1.101.3.4.3.1-2048.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha224-2.16.840.1.101.3.4.3.1-2048.apk
new file mode 100644
index 0000000..2f0002f
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha224-2.16.840.1.101.3.4.3.1-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha224-2.16.840.1.101.3.4.3.1-3072.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha224-2.16.840.1.101.3.4.3.1-3072.apk
new file mode 100644
index 0000000..13b4bcb
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha224-2.16.840.1.101.3.4.3.1-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha256-1.2.840.10040.4.1-1024.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha256-1.2.840.10040.4.1-1024.apk
new file mode 100644
index 0000000..5a7e4d3
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha256-1.2.840.10040.4.1-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha256-1.2.840.10040.4.1-2048.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha256-1.2.840.10040.4.1-2048.apk
new file mode 100644
index 0000000..edeb27d
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha256-1.2.840.10040.4.1-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha256-1.2.840.10040.4.1-3072.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha256-1.2.840.10040.4.1-3072.apk
new file mode 100644
index 0000000..ea5deea
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha256-1.2.840.10040.4.1-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha256-2.16.840.1.101.3.4.3.2-1024.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha256-2.16.840.1.101.3.4.3.2-1024.apk
new file mode 100644
index 0000000..b1bf280
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha256-2.16.840.1.101.3.4.3.2-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha256-2.16.840.1.101.3.4.3.2-2048.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha256-2.16.840.1.101.3.4.3.2-2048.apk
new file mode 100644
index 0000000..8e7415f
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha256-2.16.840.1.101.3.4.3.2-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha256-2.16.840.1.101.3.4.3.2-3072.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha256-2.16.840.1.101.3.4.3.2-3072.apk
new file mode 100644
index 0000000..65f8238
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha256-2.16.840.1.101.3.4.3.2-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha384-2.16.840.1.101.3.4.3.3-1024.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha384-2.16.840.1.101.3.4.3.3-1024.apk
new file mode 100644
index 0000000..f5db0e4
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha384-2.16.840.1.101.3.4.3.3-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha384-2.16.840.1.101.3.4.3.3-2048.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha384-2.16.840.1.101.3.4.3.3-2048.apk
new file mode 100644
index 0000000..d18a9a9
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha384-2.16.840.1.101.3.4.3.3-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha384-2.16.840.1.101.3.4.3.3-3072.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha384-2.16.840.1.101.3.4.3.3-3072.apk
new file mode 100644
index 0000000..641ba50
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha384-2.16.840.1.101.3.4.3.3-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha512-2.16.840.1.101.3.4.3.4-1024.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha512-2.16.840.1.101.3.4.3.4-1024.apk
new file mode 100644
index 0000000..dd8ab61
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha512-2.16.840.1.101.3.4.3.4-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha512-2.16.840.1.101.3.4.3.4-2048.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha512-2.16.840.1.101.3.4.3.4-2048.apk
new file mode 100644
index 0000000..129a335
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha512-2.16.840.1.101.3.4.3.4-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-dsa-sha512-2.16.840.1.101.3.4.3.4-3072.apk b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha512-2.16.840.1.101.3.4.3.4-3072.apk
new file mode 100644
index 0000000..41287d7
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-dsa-sha512-2.16.840.1.101.3.4.3.4-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha1-1.2.840.10045.2.1-p256.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha1-1.2.840.10045.2.1-p256.apk
new file mode 100644
index 0000000..bfa657a
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha1-1.2.840.10045.2.1-p256.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha1-1.2.840.10045.2.1-p384.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha1-1.2.840.10045.2.1-p384.apk
new file mode 100644
index 0000000..42ee240
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha1-1.2.840.10045.2.1-p384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha1-1.2.840.10045.2.1-p521.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha1-1.2.840.10045.2.1-p521.apk
new file mode 100644
index 0000000..fe3efbf
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha1-1.2.840.10045.2.1-p521.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha1-1.2.840.10045.4.1-p256.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha1-1.2.840.10045.4.1-p256.apk
new file mode 100644
index 0000000..e9a0d90
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha1-1.2.840.10045.4.1-p256.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha1-1.2.840.10045.4.1-p384.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha1-1.2.840.10045.4.1-p384.apk
new file mode 100644
index 0000000..848cda0
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha1-1.2.840.10045.4.1-p384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha1-1.2.840.10045.4.1-p521.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha1-1.2.840.10045.4.1-p521.apk
new file mode 100644
index 0000000..397e6db
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha1-1.2.840.10045.4.1-p521.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha224-1.2.840.10045.2.1-p256.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha224-1.2.840.10045.2.1-p256.apk
new file mode 100644
index 0000000..fff1789
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha224-1.2.840.10045.2.1-p256.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha224-1.2.840.10045.2.1-p384.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha224-1.2.840.10045.2.1-p384.apk
new file mode 100644
index 0000000..0d83e41
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha224-1.2.840.10045.2.1-p384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha224-1.2.840.10045.2.1-p521.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha224-1.2.840.10045.2.1-p521.apk
new file mode 100644
index 0000000..92f8548
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha224-1.2.840.10045.2.1-p521.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha224-1.2.840.10045.4.3.1-p256.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha224-1.2.840.10045.4.3.1-p256.apk
new file mode 100644
index 0000000..5127d60
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha224-1.2.840.10045.4.3.1-p256.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha224-1.2.840.10045.4.3.1-p384.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha224-1.2.840.10045.4.3.1-p384.apk
new file mode 100644
index 0000000..b5d9d46
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha224-1.2.840.10045.4.3.1-p384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha224-1.2.840.10045.4.3.1-p521.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha224-1.2.840.10045.4.3.1-p521.apk
new file mode 100644
index 0000000..f3aa1d6
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha224-1.2.840.10045.4.3.1-p521.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha256-1.2.840.10045.2.1-p256.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha256-1.2.840.10045.2.1-p256.apk
new file mode 100644
index 0000000..c289f6f
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha256-1.2.840.10045.2.1-p256.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha256-1.2.840.10045.2.1-p384.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha256-1.2.840.10045.2.1-p384.apk
new file mode 100644
index 0000000..b979702
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha256-1.2.840.10045.2.1-p384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha256-1.2.840.10045.2.1-p521.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha256-1.2.840.10045.2.1-p521.apk
new file mode 100644
index 0000000..9ab8bd4
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha256-1.2.840.10045.2.1-p521.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha256-1.2.840.10045.4.3.2-p256.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha256-1.2.840.10045.4.3.2-p256.apk
new file mode 100644
index 0000000..02b8a31
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha256-1.2.840.10045.4.3.2-p256.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha256-1.2.840.10045.4.3.2-p384.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha256-1.2.840.10045.4.3.2-p384.apk
new file mode 100644
index 0000000..5c4bf7b
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha256-1.2.840.10045.4.3.2-p384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha256-1.2.840.10045.4.3.2-p521.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha256-1.2.840.10045.4.3.2-p521.apk
new file mode 100644
index 0000000..3a91ee5
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha256-1.2.840.10045.4.3.2-p521.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.2.1-p256.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.2.1-p256.apk
new file mode 100644
index 0000000..c24be65
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.2.1-p256.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.2.1-p384.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.2.1-p384.apk
new file mode 100644
index 0000000..dc06637
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.2.1-p384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.2.1-p521.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.2.1-p521.apk
new file mode 100644
index 0000000..8194a87
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.2.1-p521.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.4.3.3-p256.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.4.3.3-p256.apk
new file mode 100644
index 0000000..ec4a42e
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.4.3.3-p256.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.4.3.3-p384.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.4.3.3-p384.apk
new file mode 100644
index 0000000..c3c3e0a
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.4.3.3-p384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.4.3.3-p521.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.4.3.3-p521.apk
new file mode 100644
index 0000000..5be8ac5
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.4.3.3-p521.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.2.1-p256.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.2.1-p256.apk
new file mode 100644
index 0000000..ecd9287
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.2.1-p256.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.2.1-p384.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.2.1-p384.apk
new file mode 100644
index 0000000..c2afc5a
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.2.1-p384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.2.1-p521.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.2.1-p521.apk
new file mode 100644
index 0000000..62b86b2
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.2.1-p521.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.4.3.4-p256.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.4.3.4-p256.apk
new file mode 100644
index 0000000..60090a5
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.4.3.4-p256.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.4.3.4-p384.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.4.3.4-p384.apk
new file mode 100644
index 0000000..63f6780
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.4.3.4-p384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.4.3.4-p521.apk b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.4.3.4-p521.apk
new file mode 100644
index 0000000..53ae508
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.4.3.4-p521.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-lf-in-entry-name.apk b/src/test/resources/com/android/apksig/v1-only-with-lf-in-entry-name.apk
new file mode 100644
index 0000000..7d341da
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-lf-in-entry-name.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-nul-in-entry-name.apk b/src/test/resources/com/android/apksig/v1-only-with-nul-in-entry-name.apk
new file mode 100644
index 0000000..2bcdec1
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-nul-in-entry-name.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-1024-cert-not-der.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-1024-cert-not-der.apk
new file mode 100644
index 0000000..28c8e0c
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-1024-cert-not-der.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-1024-cert-not-der2.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-1024-cert-not-der2.apk
new file mode 100644
index 0000000..ff722aa
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-1024-cert-not-der2.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-1024.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-1024.apk
new file mode 100644
index 0000000..c1dff8a
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-1024.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-1024.apk
new file mode 100644
index 0000000..fe50da7
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-16384.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-16384.apk
new file mode 100644
index 0000000..3ce1737
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-16384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-2048.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-2048.apk
new file mode 100644
index 0000000..5eb6dec
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-3072.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-3072.apk
new file mode 100644
index 0000000..451f336
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-4096.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-4096.apk
new file mode 100644
index 0000000..a99a670
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-4096.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-8192.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-8192.apk
new file mode 100644
index 0000000..10f4654
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-8192.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-1024.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-1024.apk
new file mode 100644
index 0000000..4f248bd
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-16384.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-16384.apk
new file mode 100644
index 0000000..ee14de4
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-16384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-2048.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-2048.apk
new file mode 100644
index 0000000..d7c292f
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-3072.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-3072.apk
new file mode 100644
index 0000000..5d0d11a
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-4096.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-4096.apk
new file mode 100644
index 0000000..32e28a9
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-4096.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-8192.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-8192.apk
new file mode 100644
index 0000000..10163b6
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-8192.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-1024.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-1024.apk
new file mode 100644
index 0000000..f31e649
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-16384.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-16384.apk
new file mode 100644
index 0000000..784e569
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-16384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-2048.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-2048.apk
new file mode 100644
index 0000000..a2801e5
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-3072.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-3072.apk
new file mode 100644
index 0000000..8d9fccf
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-4096.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-4096.apk
new file mode 100644
index 0000000..3e36a40
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-4096.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-8192.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-8192.apk
new file mode 100644
index 0000000..b8f979d
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-8192.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-1024.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-1024.apk
new file mode 100644
index 0000000..091cf6f
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-16384.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-16384.apk
new file mode 100644
index 0000000..4da371d
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-16384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-2048.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-2048.apk
new file mode 100644
index 0000000..43bb698
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-3072.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-3072.apk
new file mode 100644
index 0000000..a4c2b15
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-4096.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-4096.apk
new file mode 100644
index 0000000..806e0ea
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-4096.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-8192.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-8192.apk
new file mode 100644
index 0000000..efb124b
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-8192.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-1024.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-1024.apk
new file mode 100644
index 0000000..d86ac4d
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-16384.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-16384.apk
new file mode 100644
index 0000000..7656ece
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-16384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-2048.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-2048.apk
new file mode 100644
index 0000000..4971db7
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-3072.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-3072.apk
new file mode 100644
index 0000000..ed7f3f5
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-4096.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-4096.apk
new file mode 100644
index 0000000..7d47560
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-4096.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-8192.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-8192.apk
new file mode 100644
index 0000000..99bcba2
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-8192.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-1024.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-1024.apk
new file mode 100644
index 0000000..37192c3
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-16384.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-16384.apk
new file mode 100644
index 0000000..357999a
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-16384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-2048.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-2048.apk
new file mode 100644
index 0000000..a8a6b15
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-3072.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-3072.apk
new file mode 100644
index 0000000..5c7c901
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-4096.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-4096.apk
new file mode 100644
index 0000000..2774259
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-4096.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-8192.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-8192.apk
new file mode 100644
index 0000000..1df393a
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-8192.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-1024.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-1024.apk
new file mode 100644
index 0000000..46d3e61
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-16384.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-16384.apk
new file mode 100644
index 0000000..21bf67f
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-16384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-2048.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-2048.apk
new file mode 100644
index 0000000..a952f1b
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-3072.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-3072.apk
new file mode 100644
index 0000000..db6941d
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-4096.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-4096.apk
new file mode 100644
index 0000000..5bd3d0d
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-4096.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-8192.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-8192.apk
new file mode 100644
index 0000000..d8630b6
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-8192.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-1024.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-1024.apk
new file mode 100644
index 0000000..2aea3bd
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-16384.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-16384.apk
new file mode 100644
index 0000000..9fafe2c
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-16384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-2048.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-2048.apk
new file mode 100644
index 0000000..348afda
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-3072.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-3072.apk
new file mode 100644
index 0000000..db73a9b
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-4096.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-4096.apk
new file mode 100644
index 0000000..34be32d
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-4096.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-8192.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-8192.apk
new file mode 100644
index 0000000..9af7d99
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-8192.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-1024.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-1024.apk
new file mode 100644
index 0000000..9438016
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-16384.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-16384.apk
new file mode 100644
index 0000000..bb0caf0
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-16384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-2048.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-2048.apk
new file mode 100644
index 0000000..81a0258
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-3072.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-3072.apk
new file mode 100644
index 0000000..b76d7cf
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-4096.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-4096.apk
new file mode 100644
index 0000000..2583f6f
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-4096.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-8192.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-8192.apk
new file mode 100644
index 0000000..f0b47f6
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-8192.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-1024.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-1024.apk
new file mode 100644
index 0000000..8e72556
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-16384.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-16384.apk
new file mode 100644
index 0000000..d4fc6fb
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-16384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-2048.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-2048.apk
new file mode 100644
index 0000000..1e3be03
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-3072.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-3072.apk
new file mode 100644
index 0000000..ac9e972
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-4096.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-4096.apk
new file mode 100644
index 0000000..d220048
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-4096.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-8192.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-8192.apk
new file mode 100644
index 0000000..c70bce1
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-8192.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-1024.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-1024.apk
new file mode 100644
index 0000000..7c6ff19
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-16384.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-16384.apk
new file mode 100644
index 0000000..8cf9eba
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-16384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-2048.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-2048.apk
new file mode 100644
index 0000000..58676e8
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-3072.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-3072.apk
new file mode 100644
index 0000000..2f4b23c
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-4096.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-4096.apk
new file mode 100644
index 0000000..b823de3
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-4096.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-8192.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-8192.apk
new file mode 100644
index 0000000..ffa6be1
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-8192.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-1024.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-1024.apk
new file mode 100644
index 0000000..480fcc5
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-16384.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-16384.apk
new file mode 100644
index 0000000..a6089e6
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-16384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-2048.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-2048.apk
new file mode 100644
index 0000000..9de481f
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-3072.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-3072.apk
new file mode 100644
index 0000000..895e2c5
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-4096.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-4096.apk
new file mode 100644
index 0000000..70b7592
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-4096.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-8192.apk b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-8192.apk
new file mode 100644
index 0000000..475a4e6
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-8192.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-manifest.apk b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-manifest.apk
new file mode 100644
index 0000000..1d90c7e
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-manifest.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-sf.apk b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-sf.apk
new file mode 100644
index 0000000..a16e442
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-sf.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-manifest.apk b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-manifest.apk
new file mode 100644
index 0000000..29a6030
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-manifest.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-sf.apk b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-sf.apk
new file mode 100644
index 0000000..eb12bcc
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-sf.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf.apk b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf.apk
new file mode 100644
index 0000000..f347a1c
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sha1-sf.apk b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sha1-sf.apk
new file mode 100644
index 0000000..f347a1c
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sha1-sf.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v1-with-apk-sig-block-but-without-apk-sig-scheme-v2-block.apk b/src/test/resources/com/android/apksig/v1-with-apk-sig-block-but-without-apk-sig-scheme-v2-block.apk
new file mode 100644
index 0000000..a194a2c
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v1-with-apk-sig-block-but-without-apk-sig-scheme-v2-block.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-apk-sig-block-size-mismatch.apk b/src/test/resources/com/android/apksig/v2-only-apk-sig-block-size-mismatch.apk
new file mode 100644
index 0000000..e657a80
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-apk-sig-block-size-mismatch.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-cert-and-public-key-mismatch.apk b/src/test/resources/com/android/apksig/v2-only-cert-and-public-key-mismatch.apk
new file mode 100644
index 0000000..5fbe97e
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-cert-and-public-key-mismatch.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-empty.apk b/src/test/resources/com/android/apksig/v2-only-empty.apk
new file mode 100644
index 0000000..071ac7e
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-empty.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-garbage-between-cd-and-eocd.apk b/src/test/resources/com/android/apksig/v2-only-garbage-between-cd-and-eocd.apk
new file mode 100644
index 0000000..9360383
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-garbage-between-cd-and-eocd.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-max-sized-eocd-comment.apk b/src/test/resources/com/android/apksig/v2-only-max-sized-eocd-comment.apk
new file mode 100644
index 0000000..ef6cb43
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-max-sized-eocd-comment.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-missing-classes.dex.apk b/src/test/resources/com/android/apksig/v2-only-missing-classes.dex.apk
new file mode 100644
index 0000000..0f1350a
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-missing-classes.dex.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-no-certs-in-sig.apk b/src/test/resources/com/android/apksig/v2-only-no-certs-in-sig.apk
new file mode 100644
index 0000000..692c6b3
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-no-certs-in-sig.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-signatures-and-digests-block-mismatch.apk b/src/test/resources/com/android/apksig/v2-only-signatures-and-digests-block-mismatch.apk
new file mode 100644
index 0000000..fb397a4
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-signatures-and-digests-block-mismatch.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-targetSandboxVersion-2.apk b/src/test/resources/com/android/apksig/v2-only-targetSandboxVersion-2.apk
new file mode 100644
index 0000000..f3487c8
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-targetSandboxVersion-2.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-targetSandboxVersion-3.apk b/src/test/resources/com/android/apksig/v2-only-targetSandboxVersion-3.apk
new file mode 100644
index 0000000..56984c5
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-targetSandboxVersion-3.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-truncated-cd.apk b/src/test/resources/com/android/apksig/v2-only-truncated-cd.apk
new file mode 100644
index 0000000..d2e3e8d
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-truncated-cd.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-two-signers-second-signer-no-sig.apk b/src/test/resources/com/android/apksig/v2-only-two-signers-second-signer-no-sig.apk
new file mode 100644
index 0000000..1133085
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-two-signers-second-signer-no-sig.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-two-signers-second-signer-no-supported-sig.apk b/src/test/resources/com/android/apksig/v2-only-two-signers-second-signer-no-supported-sig.apk
new file mode 100644
index 0000000..2fab38a
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-two-signers-second-signer-no-supported-sig.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-two-signers.apk b/src/test/resources/com/android/apksig/v2-only-two-signers.apk
new file mode 100644
index 0000000..697b046
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-two-signers.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-unknown-pair-in-apk-sig-block.apk b/src/test/resources/com/android/apksig/v2-only-unknown-pair-in-apk-sig-block.apk
new file mode 100644
index 0000000..2904305
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-unknown-pair-in-apk-sig-block.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-dsa-sha256-1024-sig-does-not-verify.apk b/src/test/resources/com/android/apksig/v2-only-with-dsa-sha256-1024-sig-does-not-verify.apk
new file mode 100644
index 0000000..18a4409
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-dsa-sha256-1024-sig-does-not-verify.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-dsa-sha256-1024.apk b/src/test/resources/com/android/apksig/v2-only-with-dsa-sha256-1024.apk
new file mode 100644
index 0000000..9c8232f
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-dsa-sha256-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-dsa-sha256-2048.apk b/src/test/resources/com/android/apksig/v2-only-with-dsa-sha256-2048.apk
new file mode 100644
index 0000000..1d679b1
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-dsa-sha256-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-dsa-sha256-3072.apk b/src/test/resources/com/android/apksig/v2-only-with-dsa-sha256-3072.apk
new file mode 100644
index 0000000..6921479
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-dsa-sha256-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha256-p256-digest-mismatch.apk b/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha256-p256-digest-mismatch.apk
new file mode 100644
index 0000000..4cfc49c
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha256-p256-digest-mismatch.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha256-p256-sig-does-not-verify.apk b/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha256-p256-sig-does-not-verify.apk
new file mode 100644
index 0000000..0339d39
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha256-p256-sig-does-not-verify.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha256-p256.apk b/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha256-p256.apk
new file mode 100644
index 0000000..657f1f9
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha256-p256.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha256-p384.apk b/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha256-p384.apk
new file mode 100644
index 0000000..326c681
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha256-p384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha256-p521.apk b/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha256-p521.apk
new file mode 100644
index 0000000..7fcb887
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha256-p521.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha512-p256.apk b/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha512-p256.apk
new file mode 100644
index 0000000..5942916
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha512-p256.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha512-p384.apk b/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha512-p384.apk
new file mode 100644
index 0000000..82aa6ad
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha512-p384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha512-p521.apk b/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha512-p521.apk
new file mode 100644
index 0000000..fde4294
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha512-p521.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-ignorable-unsupported-sig-algs.apk b/src/test/resources/com/android/apksig/v2-only-with-ignorable-unsupported-sig-algs.apk
new file mode 100644
index 0000000..165b262
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-ignorable-unsupported-sig-algs.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk
new file mode 100644
index 0000000..ada1b00
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-1024.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-1024.apk
new file mode 100644
index 0000000..5751fbe
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-16384.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-16384.apk
new file mode 100644
index 0000000..a832366
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-16384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-2048-sig-does-not-verify.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-2048-sig-does-not-verify.apk
new file mode 100644
index 0000000..fe6be6f
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-2048-sig-does-not-verify.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-2048.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-2048.apk
new file mode 100644
index 0000000..51ed3ff
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-3072.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-3072.apk
new file mode 100644
index 0000000..a3569d6
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-4096.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-4096.apk
new file mode 100644
index 0000000..acf5cb6
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-4096.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-8192.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-8192.apk
new file mode 100644
index 0000000..1c9f131
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-8192.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-1024.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-1024.apk
new file mode 100644
index 0000000..e95e312
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-16384.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-16384.apk
new file mode 100644
index 0000000..2b332b1
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-16384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-2048.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-2048.apk
new file mode 100644
index 0000000..5e5863e
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-3072.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-3072.apk
new file mode 100644
index 0000000..d587865
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-4096-digest-mismatch.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-4096-digest-mismatch.apk
new file mode 100644
index 0000000..72f5dba
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-4096-digest-mismatch.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-4096.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-4096.apk
new file mode 100644
index 0000000..1189d8a
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-4096.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-8192.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-8192.apk
new file mode 100644
index 0000000..146534e
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-8192.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-1024.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-1024.apk
new file mode 100644
index 0000000..1331d35
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-1024.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-16384.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-16384.apk
new file mode 100644
index 0000000..a205741
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-16384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-2048-sig-does-not-verify.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-2048-sig-does-not-verify.apk
new file mode 100644
index 0000000..f90d226
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-2048-sig-does-not-verify.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-2048.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-2048.apk
new file mode 100644
index 0000000..8bdfb45
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-3072.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-3072.apk
new file mode 100644
index 0000000..d36731c
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-4096.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-4096.apk
new file mode 100644
index 0000000..8e190b4
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-4096.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-8192.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-8192.apk
new file mode 100644
index 0000000..c879ffe
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-8192.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha512-16384.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha512-16384.apk
new file mode 100644
index 0000000..d398584
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha512-16384.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha512-2048.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha512-2048.apk
new file mode 100644
index 0000000..bf8150d
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha512-2048.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha512-3072.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha512-3072.apk
new file mode 100644
index 0000000..ca11ebb
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha512-3072.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha512-4096.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha512-4096.apk
new file mode 100644
index 0000000..dfa1206
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha512-4096.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha512-8192.apk b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha512-8192.apk
new file mode 100644
index 0000000..1bdd017
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha512-8192.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-only-wrong-apk-sig-block-magic.apk b/src/test/resources/com/android/apksig/v2-only-wrong-apk-sig-block-magic.apk
new file mode 100644
index 0000000..85750d4
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-only-wrong-apk-sig-block-magic.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-stripped-with-ignorable-signing-schemes.apk b/src/test/resources/com/android/apksig/v2-stripped-with-ignorable-signing-schemes.apk
new file mode 100644
index 0000000..634255b
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-stripped-with-ignorable-signing-schemes.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v2-stripped.apk b/src/test/resources/com/android/apksig/v2-stripped.apk
new file mode 100644
index 0000000..0d04d93
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v2-stripped.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/weird-compression-method.apk b/src/test/resources/com/android/apksig/weird-compression-method.apk
new file mode 100644
index 0000000..33f9af9
--- /dev/null
+++ b/src/test/resources/com/android/apksig/weird-compression-method.apk
Binary files differ