release-request-c5216b17-dc50-4b24-831c-dc696ed657cc-for-git_oc-dr1-release-4133426 snap-temp-L83500000077479774
Change-Id: Ie2657ec4e7176c7f8b950f0aed6dd5d5cce75946
diff --git a/src/apksigner/java/com/android/apksigner/ApkSignerTool.java b/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
index 71e147c..9aed804 100644
--- a/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
+++ b/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
@@ -68,7 +68,7 @@
*/
public class ApkSignerTool {
- private static final String VERSION = "0.6";
+ 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";
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/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/test/java/com/android/apksig/ApkSignerTest.java b/src/test/java/com/android/apksig/ApkSignerTest.java
index bcd0fe1..513023e 100644
--- a/src/test/java/com/android/apksig/ApkSignerTest.java
+++ b/src/test/java/com/android/apksig/ApkSignerTest.java
@@ -313,6 +313,52 @@
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.
diff --git a/src/test/java/com/android/apksig/ApkVerifierTest.java b/src/test/java/com/android/apksig/ApkVerifierTest.java
index 0c1cc8b..2bb3487 100644
--- a/src/test/java/com/android/apksig/ApkVerifierTest.java
+++ b/src/test/java/com/android/apksig/ApkVerifierTest.java
@@ -35,6 +35,7 @@
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;
@@ -65,6 +66,8 @@
@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);
@@ -493,6 +496,35 @@
// 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());
@@ -502,6 +534,31 @@
}
@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.
@@ -629,6 +686,34 @@
"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);
@@ -793,4 +878,13 @@
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/internal/util/ArrayBackedByteBufferSinkTest.java b/src/test/java/com/android/apksig/internal/util/ArrayBackedByteBufferSinkTest.java
index 3256728..e554e19 100644
--- a/src/test/java/com/android/apksig/internal/util/ArrayBackedByteBufferSinkTest.java
+++ b/src/test/java/com/android/apksig/internal/util/ArrayBackedByteBufferSinkTest.java
@@ -17,7 +17,10 @@
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) {
diff --git a/src/test/java/com/android/apksig/internal/util/DirectByteBufferSinkTest.java b/src/test/java/com/android/apksig/internal/util/DirectByteBufferSinkTest.java
index d416508..0b8e975 100644
--- a/src/test/java/com/android/apksig/internal/util/DirectByteBufferSinkTest.java
+++ b/src/test/java/com/android/apksig/internal/util/DirectByteBufferSinkTest.java
@@ -17,7 +17,10 @@
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) {
diff --git a/src/test/java/com/android/apksig/util/InMemoryDataSinkDataSourceTest.java b/src/test/java/com/android/apksig/util/InMemoryDataSinkDataSourceTest.java
index bce9765..33243e9 100644
--- a/src/test/java/com/android/apksig/util/InMemoryDataSinkDataSourceTest.java
+++ b/src/test/java/com/android/apksig/util/InMemoryDataSinkDataSourceTest.java
@@ -17,10 +17,13 @@
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 {
diff --git a/src/test/java/com/android/apksig/util/InMemoryDataSinkTest.java b/src/test/java/com/android/apksig/util/InMemoryDataSinkTest.java
index b75926d..f48510a 100644
--- a/src/test/java/com/android/apksig/util/InMemoryDataSinkTest.java
+++ b/src/test/java/com/android/apksig/util/InMemoryDataSinkTest.java
@@ -18,10 +18,13 @@
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
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/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-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-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/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