aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2024-01-17 22:15:00 -0800
committerXin Li <delphij@google.com>2024-01-17 22:15:00 -0800
commitd7238cfb30b461bcf4718161aff88bf924082ddb (patch)
tree8e658882980cc29204c4ac32120285f049903b21
parent24e3075e68ebe17c0b529bb24bfda819db5e2f3b (diff)
parent141edb36b7ebb8533398591d4f0a21522aea6ea6 (diff)
downloadapksig-d7238cfb30b461bcf4718161aff88bf924082ddb.tar.gz
Merge Android 24Q1 Release (ab/11220357)temp_319669529
Bug: 319669529 Merged-In: I5cb455d7ed18f13bd7a5885bd57aa29ec20fee2c Change-Id: Ib2f81cd63a741a25c67a6114fefbf4a47a968a91
-rw-r--r--src/main/java/com/android/apksig/ApkVerifier.java60
-rw-r--r--src/main/java/com/android/apksig/internal/apk/v4/V4SchemeSigner.java83
-rw-r--r--src/test/java/com/android/apksig/ApkSignerTest.java36
-rw-r--r--src/test/java/com/android/apksig/ApkVerifierTest.java66
-rw-r--r--src/test/java/com/android/apksig/internal/util/Resources.java18
-rw-r--r--src/test/resources/com/android/apksig/rsa-2048-to-4096-lineage-2-signersbin0 -> 2370 bytes
-rw-r--r--src/test/resources/com/android/apksig/v41-digest-mismatched-with-v31.apkbin0 -> 16791 bytes
-rw-r--r--src/test/resources/com/android/apksig/v41-digest-mismatched-with-v31.apk.idsigbin0 -> 7931 bytes
8 files changed, 220 insertions, 43 deletions
diff --git a/src/main/java/com/android/apksig/ApkVerifier.java b/src/main/java/com/android/apksig/ApkVerifier.java
index 078996a..50b3d9f 100644
--- a/src/main/java/com/android/apksig/ApkVerifier.java
+++ b/src/main/java/com/android/apksig/ApkVerifier.java
@@ -27,6 +27,7 @@ import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_APK_S
import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V4;
import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_JAR_SIGNATURE_SCHEME;
import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_SOURCE_STAMP;
+import static com.android.apksig.internal.apk.ApkSigningBlockUtils.toHex;
import static com.android.apksig.internal.apk.v1.V1SchemeConstants.MANIFEST_ENTRY_NAME;
import static com.android.apksig.internal.apk.v3.V3SchemeConstants.MIN_SDK_WITH_V31_SUPPORT;
@@ -510,21 +511,36 @@ public class ApkVerifier {
List<ApkSigningBlockUtils.Result.SignerInfo.ContentDigest> digestsFromV4 =
v4Signers.get(0).getContentDigests();
if (digestsFromV4.size() != 1) {
- result.addError(Issue.V4_SIG_V2_V3_DIGESTS_MISMATCH);
+ result.addError(Issue.V4_SIG_UNEXPECTED_DIGESTS, digestsFromV4.size());
+ if (digestsFromV4.isEmpty()) {
+ return result;
+ }
}
final byte[] digestFromV4 = digestsFromV4.get(0).getValue();
if (result.isVerifiedUsingV3Scheme()) {
- int expectedSize = result.isVerifiedUsingV31Scheme() ? 2 : 1;
+ final boolean isV31 = result.isVerifiedUsingV31Scheme();
+ final int expectedSize = isV31 ? 2 : 1;
if (v4Signers.size() != expectedSize) {
- result.addError(Issue.V4_SIG_MULTIPLE_SIGNERS);
+ result.addError(isV31 ? Issue.V41_SIG_NEEDS_TWO_SIGNERS
+ : Issue.V4_SIG_MULTIPLE_SIGNERS);
+ return result;
}
checkV4Signer(result.getV3SchemeSigners(), v4Signers.get(0).mCerts, digestFromV4,
result);
- if (result.isVerifiedUsingV31Scheme()) {
+ if (isV31) {
+ List<ApkSigningBlockUtils.Result.SignerInfo.ContentDigest> digestsFromV41 =
+ v4Signers.get(1).getContentDigests();
+ if (digestsFromV41.size() != 1) {
+ result.addError(Issue.V4_SIG_UNEXPECTED_DIGESTS, digestsFromV41.size());
+ if (digestsFromV41.isEmpty()) {
+ return result;
+ }
+ }
+ final byte[] digestFromV41 = digestsFromV41.get(0).getValue();
checkV4Signer(result.getV31SchemeSigners(), v4Signers.get(1).mCerts,
- digestFromV4, result);
+ digestFromV41, result);
}
} else if (result.isVerifiedUsingV2Scheme()) {
if (v4Signers.size() != 1) {
@@ -543,7 +559,8 @@ public class ApkVerifier {
final byte[] digestFromV2 = pickBestDigestForV4(
v2Signers.get(0).getContentDigests());
if (!Arrays.equals(digestFromV4, digestFromV2)) {
- result.addError(Issue.V4_SIG_V2_V3_DIGESTS_MISMATCH);
+ result.addError(Issue.V4_SIG_V2_V3_DIGESTS_MISMATCH, 2, toHex(digestFromV2),
+ toHex(digestFromV4));
}
} else {
throw new RuntimeException("V4 signature must be also verified with V2/V3");
@@ -1135,7 +1152,8 @@ public class ApkVerifier {
// Compare digests.
final byte[] digestFromV3 = pickBestDigestForV4(v3Signers.get(0).getContentDigests());
if (!Arrays.equals(digestFromV4, digestFromV3)) {
- result.addError(Issue.V4_SIG_V2_V3_DIGESTS_MISMATCH);
+ result.addError(Issue.V4_SIG_V2_V3_DIGESTS_MISMATCH, 3, toHex(digestFromV3),
+ toHex(digestFromV4));
}
}
@@ -3124,14 +3142,40 @@ public class ApkVerifier {
"V4 signature only supports one signer"),
/**
+ * V4.1 signature requires two signers to match the v3 and the v3.1.
+ */
+ V41_SIG_NEEDS_TWO_SIGNERS("V4.1 signature requires two signers"),
+
+ /**
* The signer used to sign APK Signature Scheme V2/V3 signature does not match the signer
* used to sign APK Signature Scheme V4 signature.
*/
V4_SIG_V2_V3_SIGNERS_MISMATCH(
"V4 signature and V2/V3 signature have mismatched certificates"),
+ /**
+ * The v4 signature's digest does not match the digest from the corresponding v2 / v3
+ * signature.
+ *
+ * <ul>
+ * <li>Parameter 1: Signature scheme of mismatched digest ({@code int})
+ * <li>Parameter 2: v2/v3 digest ({@code String})
+ * <li>Parameter 3: v4 digest ({@code String})
+ * </ul>
+ */
V4_SIG_V2_V3_DIGESTS_MISMATCH(
- "V4 signature and V2/V3 signature have mismatched digests"),
+ "V4 signature and V%1$d signature have mismatched digests, V%1$d digest: %2$s, V4"
+ + " digest: %3$s"),
+
+ /**
+ * The v4 signature does not contain the expected number of digests.
+ *
+ * <ul>
+ * <li>Parameter 1: Number of digests found ({@code int})
+ * </ul>
+ */
+ V4_SIG_UNEXPECTED_DIGESTS(
+ "V4 signature does not have the expected number of digests, found %1$d"),
/**
* The v4 signature format version isn't the same as the tool's current version, something
diff --git a/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeSigner.java b/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeSigner.java
index 2f9ecb3..7bf952d 100644
--- a/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeSigner.java
+++ b/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeSigner.java
@@ -16,8 +16,12 @@
package com.android.apksig.internal.apk.v4;
+import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2;
+import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3;
+import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V31;
import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeCertificates;
import static com.android.apksig.internal.apk.v2.V2SchemeConstants.APK_SIGNATURE_SCHEME_V2_BLOCK_ID;
+import static com.android.apksig.internal.apk.v3.V3SchemeConstants.APK_SIGNATURE_SCHEME_V31_BLOCK_ID;
import static com.android.apksig.internal.apk.v3.V3SchemeConstants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID;
import com.android.apksig.apk.ApkUtils;
@@ -26,7 +30,6 @@ import com.android.apksig.internal.apk.ContentDigestAlgorithm;
import com.android.apksig.internal.apk.SignatureAlgorithm;
import com.android.apksig.internal.apk.SignatureInfo;
import com.android.apksig.internal.apk.v2.V2SchemeVerifier;
-import com.android.apksig.internal.apk.v3.V3SchemeConstants;
import com.android.apksig.internal.apk.v3.V3SchemeSigner;
import com.android.apksig.internal.apk.v3.V3SchemeVerifier;
import com.android.apksig.internal.util.Pair;
@@ -43,11 +46,12 @@ import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.CertificateEncodingException;
-import java.util.Arrays;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -136,8 +140,9 @@ public abstract class V4SchemeSigner {
final long fileSize = apkContent.size();
- // Obtaining first supported digest from v2/v3 blocks (SHA256 or SHA512).
- final byte[] apkDigest = getApkDigest(apkContent);
+ // Obtaining the strongest supported digest for each of the v2/v3/v3.1 blocks
+ // (CHUNKED_SHA256 or CHUNKED_SHA512).
+ final Map<Integer, byte[]> apkDigests = getApkDigests(apkContent);
// Obtaining the merkle tree and the root hash in verity format.
ApkSigningBlockUtils.VerityTreeAndDigest verityContentDigestInfo =
@@ -157,7 +162,7 @@ public abstract class V4SchemeSigner {
// Generating SigningInfo and combining everything into V4Signature.
final V4Signature signature;
try {
- signature = generateSignature(signerConfig, hashingInfo, apkDigest, additionalData,
+ signature = generateSignature(signerConfig, hashingInfo, apkDigests, additionalData,
fileSize);
} catch (InvalidKeyException | SignatureException | CertificateEncodingException e) {
throw new InvalidKeyException("Signer failed", e);
@@ -209,16 +214,24 @@ public abstract class V4SchemeSigner {
private static V4Signature generateSignature(
SignerConfig signerConfig,
V4Signature.HashingInfo hashingInfo,
- byte[] apkDigest, byte[] additionalData, long fileSize)
+ Map<Integer, byte[]> apkDigests, byte[] additionalData, long fileSize)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException,
CertificateEncodingException {
+ byte[] apkDigest = apkDigests.containsKey(VERSION_APK_SIGNATURE_SCHEME_V3)
+ ? apkDigests.get(VERSION_APK_SIGNATURE_SCHEME_V3)
+ : apkDigests.get(VERSION_APK_SIGNATURE_SCHEME_V2);
final V4Signature.SigningInfo signingInfo = generateSigningInfo(signerConfig.v4Config,
hashingInfo, apkDigest, additionalData, fileSize);
final V4Signature.SigningInfos signingInfos;
if (signerConfig.v41Config != null) {
+ if (!apkDigests.containsKey(VERSION_APK_SIGNATURE_SCHEME_V31)) {
+ throw new IllegalStateException(
+ "V4.1 cannot be signed without a V3.1 content digest");
+ }
+ apkDigest = apkDigests.get(VERSION_APK_SIGNATURE_SCHEME_V31);
final V4Signature.SigningInfoBlock extSigningBlock = new V4Signature.SigningInfoBlock(
- V3SchemeConstants.APK_SIGNATURE_SCHEME_V31_BLOCK_ID,
+ APK_SIGNATURE_SCHEME_V31_BLOCK_ID,
generateSigningInfo(signerConfig.v41Config, hashingInfo, apkDigest,
additionalData, fileSize).toByteArray());
signingInfos = new V4Signature.SigningInfos(signingInfo, extSigningBlock);
@@ -230,8 +243,16 @@ public abstract class V4SchemeSigner {
signingInfos.toByteArray());
}
- // Get digest by parsing the V2/V3-signed apk and choosing the first digest of supported type.
- private static byte[] getApkDigest(DataSource apk) throws IOException {
+ /**
+ * Returns a {@code Map} from the APK signature scheme version to a {@code byte[]} of the
+ * strongest supported content digest found in that version's signature block for the V2,
+ * V3, and V3.1 signatures in the provided {@code apk}.
+ *
+ * <p>If a supported content digest algorithm is not found in any of the signature blocks,
+ * or if the APK is not signed by any of these signature schemes, then an {@code IOException}
+ * is thrown.
+ */
+ private static Map<Integer, byte[]> getApkDigests(DataSource apk) throws IOException {
ApkUtils.ZipSections zipSections;
try {
zipSections = ApkUtils.findZipSections(apk);
@@ -239,34 +260,60 @@ public abstract class V4SchemeSigner {
throw new IOException("Malformed APK: not a ZIP archive", e);
}
- final SignatureException v3Exception;
+ Map<Integer, byte[]> sigSchemeToDigest = new HashMap<>(1);
+ try {
+ byte[] digest = getBestV3Digest(apk, zipSections, VERSION_APK_SIGNATURE_SCHEME_V31);
+ sigSchemeToDigest.put(VERSION_APK_SIGNATURE_SCHEME_V31, digest);
+ } catch (SignatureException expected) {
+ // It is expected to catch a SignatureException if the APK does not have a v3.1
+ // signature.
+ }
+
+ SignatureException v3Exception = null;
try {
- return getBestV3Digest(apk, zipSections);
+ byte[] digest = getBestV3Digest(apk, zipSections, VERSION_APK_SIGNATURE_SCHEME_V3);
+ sigSchemeToDigest.put(VERSION_APK_SIGNATURE_SCHEME_V3, digest);
} catch (SignatureException e) {
v3Exception = e;
}
- final SignatureException v2Exception;
+ SignatureException v2Exception = null;
try {
- return getBestV2Digest(apk, zipSections);
+ byte[] digest = getBestV2Digest(apk, zipSections);
+ sigSchemeToDigest.put(VERSION_APK_SIGNATURE_SCHEME_V2, digest);
} catch (SignatureException e) {
v2Exception = e;
}
+ if (sigSchemeToDigest.size() > 0) {
+ return sigSchemeToDigest;
+ }
+
throw new IOException(
"Failed to obtain v2/v3 digest, v3 exception: " + v3Exception + ", v2 exception: "
+ v2Exception);
}
- private static byte[] getBestV3Digest(DataSource apk, ApkUtils.ZipSections zipSections)
- throws SignatureException {
+ private static byte[] getBestV3Digest(DataSource apk, ApkUtils.ZipSections zipSections,
+ int v3SchemeVersion) throws SignatureException {
final Set<ContentDigestAlgorithm> contentDigestsToVerify = new HashSet<>(1);
final ApkSigningBlockUtils.Result result = new ApkSigningBlockUtils.Result(
- ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3);
+ v3SchemeVersion);
+ final int blockId;
+ switch (v3SchemeVersion) {
+ case VERSION_APK_SIGNATURE_SCHEME_V31:
+ blockId = APK_SIGNATURE_SCHEME_V31_BLOCK_ID;
+ break;
+ case VERSION_APK_SIGNATURE_SCHEME_V3:
+ blockId = APK_SIGNATURE_SCHEME_V3_BLOCK_ID;
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Invalid V3 scheme provided: " + v3SchemeVersion);
+ }
try {
final SignatureInfo signatureInfo =
- ApkSigningBlockUtils.findSignature(apk, zipSections,
- APK_SIGNATURE_SCHEME_V3_BLOCK_ID, result);
+ ApkSigningBlockUtils.findSignature(apk, zipSections, blockId, result);
final ByteBuffer apkSignatureSchemeV3Block = signatureInfo.signatureBlock;
V3SchemeVerifier.parseSigners(apkSignatureSchemeV3Block, contentDigestsToVerify,
result);
diff --git a/src/test/java/com/android/apksig/ApkSignerTest.java b/src/test/java/com/android/apksig/ApkSignerTest.java
index 0b6702a..525f38f 100644
--- a/src/test/java/com/android/apksig/ApkSignerTest.java
+++ b/src/test/java/com/android/apksig/ApkSignerTest.java
@@ -55,7 +55,6 @@ import com.android.apksig.util.DataSource;
import com.android.apksig.util.DataSources;
import com.android.apksig.zip.ZipFormatException;
-import java.security.InvalidKeyException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
@@ -102,6 +101,8 @@ public class ApkSignerTest {
static final String SECOND_RSA_2048_SIGNER_RESOURCE_NAME = "rsa-2048_2";
static final String THIRD_RSA_2048_SIGNER_RESOURCE_NAME = "rsa-2048_3";
+ static final String FIRST_RSA_4096_SIGNER_RESOURCE_NAME = "rsa-4096";
+
private static final String EC_P256_SIGNER_RESOURCE_NAME = "ec-p256";
private static final String EC_P256_2_SIGNER_RESOURCE_NAME = "ec-p256_2";
@@ -117,6 +118,8 @@ public class ApkSignerTest {
"rsa-2048-lineage-3-signers-1-no-caps";
private static final String LINEAGE_RSA_2048_2_SIGNERS_2_3_RESOURCE_NAME =
"rsa-2048-lineage-2-signers-2-3";
+ private static final String LINEAGE_RSA_2048_TO_RSA_4096_RESOURCE_NAME =
+ "rsa-2048-to-4096-lineage-2-signers";
private static final String LINEAGE_EC_P256_2_SIGNERS_RESOURCE_NAME =
"ec-p256-lineage-2-signers";
@@ -3050,6 +3053,37 @@ public class ApkSignerTest {
}
@Test
+ public void testV41_rotationWithDifferentDigestAlgos_v41UsesCorrectDigest() throws Exception {
+ // When signing an APK, the digest algorithm is determined by the number of bits in the
+ // signing key to ensure the digest is not weaker than the key. If an original signing key
+ // meets the requirements for the CHUNKED_SHA256 digest and the rotated signing key
+ // meets the requirements for CHUNKED_SHA512, then the v3.0 and v3.1 signing blocks will
+ // use different digests. The v4.1 signature must use the content digest from the v3.1
+ // block since that's the digest that will be used to verify the v4.1 signature on all
+ // platform versions that support the v3.1 signer.
+ List<ApkSigner.SignerConfig> rsa2048SignerConfigWithLineage =
+ Arrays.asList(
+ getDefaultSignerConfigFromResources(FIRST_RSA_2048_SIGNER_RESOURCE_NAME),
+ getDefaultSignerConfigFromResources(FIRST_RSA_4096_SIGNER_RESOURCE_NAME));
+ SigningCertificateLineage lineage =
+ Resources.toSigningCertificateLineage(
+ ApkSignerTest.class, LINEAGE_RSA_2048_TO_RSA_4096_RESOURCE_NAME);
+
+ File signedApk = sign("original.apk",
+ new ApkSigner.Builder(rsa2048SignerConfigWithLineage)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true)
+ .setV4SigningEnabled(true)
+ .setMinSdkVersionForRotation(AndroidSdkVersion.T)
+ .setSigningCertificateLineage(lineage));
+ ApkVerifier.Result result = verify(signedApk, null);
+
+ assertResultContainsV4Signers(result, FIRST_RSA_2048_SIGNER_RESOURCE_NAME,
+ FIRST_RSA_4096_SIGNER_RESOURCE_NAME);
+ }
+
+ @Test
public void
testSourceStampTimestamp_signWithSourceStampAndTimestampDefault_validTimestampValue()
throws Exception {
diff --git a/src/test/java/com/android/apksig/ApkVerifierTest.java b/src/test/java/com/android/apksig/ApkVerifierTest.java
index 3242f5e..763fee0 100644
--- a/src/test/java/com/android/apksig/ApkVerifierTest.java
+++ b/src/test/java/com/android/apksig/ApkVerifierTest.java
@@ -44,13 +44,17 @@ import com.android.apksig.internal.util.Resources;
import com.android.apksig.util.DataSource;
import com.android.apksig.util.DataSources;
+import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.Provider;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.Assume;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -75,6 +79,8 @@ import java.util.stream.Stream;
@RunWith(JUnit4.class)
public class ApkVerifierTest {
+ @Rule
+ public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
private static final String[] DSA_KEY_NAMES = {"1024", "2048", "3072"};
private static final String[] DSA_KEY_NAMES_1024_AND_SMALLER = {"1024"};
@@ -574,9 +580,10 @@ public class ApkVerifierTest {
// 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);
+ assertVerificationIssue(
+ verify("v2-only-with-rsa-pkcs1-sha256-2048-sig-does-not-verify.apk"),
+ true,
+ Issue.V2_SIG_VERIFY_EXCEPTION, Issue.V2_SIG_DID_NOT_VERIFY);
// Bitflip in the ECDSA signature. Based on v2-only-with-ecdsa-sha256-p256.apk.
assertVerificationFailure(
@@ -1843,6 +1850,16 @@ public class ApkVerifierTest {
assertTrue(result.isVerifiedUsingV31Scheme());
}
+ @Test
+ public void verify41_v41DigestMismatchedWithV31_reportsError() throws Exception {
+ // This test verifies a digest mismatch between the v4.1 signature and the v3.1 signature
+ // is properly reported during v4 signature verification.
+ ApkVerifier.Result result = verifyWithV4Signature("v41-digest-mismatched-with-v31.apk",
+ "v41-digest-mismatched-with-v31.apk.idsig");
+
+ assertVerificationFailure(result, Issue.V4_SIG_V2_V3_DIGESTS_MISMATCH);
+ }
+
@Test(expected = IOException.class)
public void verify_largeFileSize_doesNotFailWithOOMError() throws Exception {
// During V1 signature verification, each file needs to be uncompressed to calculate
@@ -1972,6 +1989,21 @@ public class ApkVerifierTest {
return builder.build().verify();
}
+ private ApkVerifier.Result verifyWithV4Signature(
+ String apkFilenameInResources,
+ String v4SignatureFile)
+ throws IOException, ApkFormatException, NoSuchAlgorithmException, URISyntaxException {
+ byte[] apkBytes = Resources.toByteArray(getClass(), apkFilenameInResources);
+
+ ApkVerifier.Builder builder =
+ new ApkVerifier.Builder(DataSources.asDataSource(ByteBuffer.wrap(apkBytes)));
+ if (v4SignatureFile != null) {
+ builder.setV4SignatureFile(
+ Resources.toFile(getClass(), v4SignatureFile, mTemporaryFolder));
+ }
+ return builder.build().verify();
+ }
+
private ApkVerifier.Result verifySourceStamp(String apkFilenameInResources) throws Exception {
return verifySourceStamp(apkFilenameInResources, null, null, null);
}
@@ -2089,28 +2121,30 @@ public class ApkVerifierTest {
}
static void assertVerificationFailure(ApkVerifier.Result result, Issue expectedIssue) {
- assertVerificationIssue(result, expectedIssue, true);
+ assertVerificationIssue(result, true, expectedIssue);
}
static void assertVerificationWarning(ApkVerifier.Result result, Issue expectedIssue) {
- assertVerificationIssue(result, expectedIssue, false);
+ assertVerificationIssue(result, false, expectedIssue);
}
/**
- * Asserts the provided {@code result} contains the {@code expectedIssue}; if {@code
+ * Asserts the provided {@code result} contains one of the {@code expectedIssues}; if {@code
* verifyError} is set to {@code true} then the specified {@link Issue} will be expected as an
* error, otherwise it will be expected as a warning.
*/
- private static void assertVerificationIssue(ApkVerifier.Result result, Issue expectedIssue,
- boolean verifyError) {
+ private static void assertVerificationIssue(ApkVerifier.Result result, boolean verifyError,
+ Issue... expectedIssues) {
+ List<Issue> expectedIssuesList = expectedIssues != null
+ ? Arrays.asList(expectedIssues) : Collections.emptyList();
if (result.isVerified() && verifyError) {
- fail("APK verification succeeded instead of failing with " + expectedIssue);
+ fail("APK verification succeeded instead of failing with " + expectedIssuesList);
return;
}
StringBuilder msg = new StringBuilder();
for (IssueWithParams issue : (verifyError ? result.getErrors() : result.getWarnings())) {
- if (issue.getIssue().equals(expectedIssue)) {
+ if (expectedIssuesList.contains(issue.getIssue())) {
return;
}
if (msg.length() > 0) {
@@ -2122,7 +2156,7 @@ public class ApkVerifierTest {
String signerName = signer.getName();
for (ApkVerifier.IssueWithParams issue : (verifyError ? signer.getErrors()
: signer.getWarnings())) {
- if (issue.getIssue().equals(expectedIssue)) {
+ if (expectedIssuesList.contains(issue.getIssue())) {
return;
}
if (msg.length() > 0) {
@@ -2140,7 +2174,7 @@ public class ApkVerifierTest {
String signerName = "signer #" + (signer.getIndex() + 1);
for (IssueWithParams issue : (verifyError ? signer.getErrors()
: signer.getWarnings())) {
- if (issue.getIssue().equals(expectedIssue)) {
+ if (expectedIssuesList.contains(issue.getIssue())) {
return;
}
if (msg.length() > 0) {
@@ -2156,7 +2190,7 @@ public class ApkVerifierTest {
String signerName = "signer #" + (signer.getIndex() + 1);
for (IssueWithParams issue : (verifyError ? signer.getErrors()
: signer.getWarnings())) {
- if (issue.getIssue().equals(expectedIssue)) {
+ if (expectedIssuesList.contains(issue.getIssue())) {
return;
}
if (msg.length() > 0) {
@@ -2172,7 +2206,7 @@ public class ApkVerifierTest {
String signerName = "signer #" + (signer.getIndex() + 1);
for (IssueWithParams issue : (verifyError ? signer.getErrors()
: signer.getWarnings())) {
- if (issue.getIssue().equals(expectedIssue)) {
+ if (expectedIssuesList.contains(issue.getIssue())) {
return;
}
if (msg.length() > 0) {
@@ -2184,14 +2218,14 @@ public class ApkVerifierTest {
.append(issue);
}
}
- if (expectedIssue == null && msg.length() == 0) {
+ if ((expectedIssuesList.isEmpty() || expectedIssues[0] == null) && msg.length() == 0) {
return;
}
fail(
"APK failed verification for the wrong reason"
+ ". Expected: "
- + expectedIssue
+ + expectedIssuesList
+ ", actual: "
+ msg);
}
diff --git a/src/test/java/com/android/apksig/internal/util/Resources.java b/src/test/java/com/android/apksig/internal/util/Resources.java
index 82bf76f..ac00946 100644
--- a/src/test/java/com/android/apksig/internal/util/Resources.java
+++ b/src/test/java/com/android/apksig/internal/util/Resources.java
@@ -20,8 +20,13 @@ import com.android.apksig.ApkSignerTest;
import com.android.apksig.SigningCertificateLineage;
import com.android.apksig.util.DataSource;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
@@ -140,4 +145,17 @@ public final class Resources {
DataSource lineageDataSource = toDataSource(cls, fileResourceName);
return SigningCertificateLineage.readFromDataSource(lineageDataSource);
}
+
+ public static File toFile(Class<?> cls, String fileResourceName,
+ TemporaryFolder temporaryFolder) throws IOException {
+ File outFile = temporaryFolder.newFile();
+ try (InputStream in = cls.getResourceAsStream(fileResourceName);
+ OutputStream out = new FileOutputStream(outFile)) {
+ if (in == null) {
+ throw new IllegalArgumentException("Resource not found: " + fileResourceName);
+ }
+ in.transferTo(out);
+ return outFile;
+ }
+ }
}
diff --git a/src/test/resources/com/android/apksig/rsa-2048-to-4096-lineage-2-signers b/src/test/resources/com/android/apksig/rsa-2048-to-4096-lineage-2-signers
new file mode 100644
index 0000000..d98b2db
--- /dev/null
+++ b/src/test/resources/com/android/apksig/rsa-2048-to-4096-lineage-2-signers
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v41-digest-mismatched-with-v31.apk b/src/test/resources/com/android/apksig/v41-digest-mismatched-with-v31.apk
new file mode 100644
index 0000000..348bb1d
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v41-digest-mismatched-with-v31.apk
Binary files differ
diff --git a/src/test/resources/com/android/apksig/v41-digest-mismatched-with-v31.apk.idsig b/src/test/resources/com/android/apksig/v41-digest-mismatched-with-v31.apk.idsig
new file mode 100644
index 0000000..d2e1399
--- /dev/null
+++ b/src/test/resources/com/android/apksig/v41-digest-mismatched-with-v31.apk.idsig
Binary files differ