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