diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/main/java/com/android/apksig/ApkVerifier.java | 60 | ||||
-rw-r--r-- | src/main/java/com/android/apksig/internal/apk/v4/V4SchemeSigner.java | 83 | ||||
-rw-r--r-- | src/test/java/com/android/apksig/ApkSignerTest.java | 36 | ||||
-rw-r--r-- | src/test/java/com/android/apksig/ApkVerifierTest.java | 66 | ||||
-rw-r--r-- | src/test/java/com/android/apksig/internal/util/Resources.java | 18 | ||||
-rw-r--r-- | src/test/resources/com/android/apksig/rsa-2048-to-4096-lineage-2-signers | bin | 0 -> 2370 bytes | |||
-rw-r--r-- | src/test/resources/com/android/apksig/v41-digest-mismatched-with-v31.apk | bin | 0 -> 16791 bytes | |||
-rw-r--r-- | src/test/resources/com/android/apksig/v41-digest-mismatched-with-v31.apk.idsig | bin | 0 -> 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 Binary files differnew file mode 100644 index 0000000..d98b2db --- /dev/null +++ b/src/test/resources/com/android/apksig/rsa-2048-to-4096-lineage-2-signers 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 Binary files differnew file mode 100644 index 0000000..348bb1d --- /dev/null +++ b/src/test/resources/com/android/apksig/v41-digest-mismatched-with-v31.apk 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 Binary files differnew file mode 100644 index 0000000..d2e1399 --- /dev/null +++ b/src/test/resources/com/android/apksig/v41-digest-mismatched-with-v31.apk.idsig |