blob: 4d0e202a73a8707c23fb317e739556a6f4d3e053 [file] [log] [blame]
/*
* 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 android.appsecurity.cts;
import static org.junit.Assert.fail;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assume.assumeTrue;
import android.platform.test.annotations.AsbSecurityTest;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.host.HostFlagsValueProvider;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.compatibility.common.util.CddTest;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.testtype.junit4.DeviceParameterizedRunner;
import com.android.tradefed.util.FileUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Locale;
import java.util.Objects;
import java.util.stream.Stream;
import junitparams.Parameters;
/**
* Tests for APK signature verification during installation.
*/
@Presubmit
@RunWith(DeviceParameterizedRunner.class)
public class PkgInstallSignatureVerificationTest extends BaseAppSecurityTest {
private static final String TEST_PKG = "android.appsecurity.cts.tinyapp";
private static final String TEST_PKG2 = "android.appsecurity.cts.tinyapp2";
private static final String COMPANION_TEST_PKG = "android.appsecurity.cts.tinyapp_companion";
private static final String COMPANION2_TEST_PKG = "android.appsecurity.cts.tinyapp_companion2";
private static final String COMPANION3_TEST_PKG = "android.appsecurity.cts.tinyapp_companion3";
private static final String DEVICE_TESTS_APK = "CtsV3SigningSchemeRotationTest.apk";
private static final String DEVICE_TESTS_PKG = "android.appsecurity.cts.v3rotationtests";
private static final String DEVICE_TESTS_CLASS = DEVICE_TESTS_PKG + ".V3RotationTest";
private static final String SERVICE_PKG = "android.appsecurity.cts.keyrotationtest";
private static final String SERVICE_TEST_PKG = "android.appsecurity.cts.keyrotationtest.test";
private static final String SERVICE_TEST_CLASS =
SERVICE_TEST_PKG + ".SignatureQueryServiceInstrumentationTest";
private static final String TEST_APK_RESOURCE_PREFIX = "/pkgsigverify/";
private static final String INSTALL_ARG_FORCE_QUERYABLE = "--force-queryable";
private static final String[] DSA_KEY_NAMES = {"1024", "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"};
private static final boolean INCREMENTAL = true;
private static final boolean NON_INCREMENTAL = false;
private static Object[] installOnIncremental() {
// Incremental and Non-Incremental.
return new Boolean[][]{{INCREMENTAL}, {NON_INCREMENTAL}};
}
private boolean mUseIncrementalForInstallWithIdsig;
@Rule
public final CheckFlagsRule mCheckFlagsRule =
HostFlagsValueProvider.createCheckFlagsRule(this::getDevice);
@Before
public void setUp() throws Exception {
Utils.prepareSingleUser(getDevice());
uninstallPackage();
uninstallCompanionPackages();
installDeviceTestPkg();
}
@After
public void tearDown() throws Exception {
uninstallPackages();
}
@Test
public void testInstallOriginalSucceeds() throws Exception {
// APK signed with v1 and v2 schemes. Obtained by building
// cts/hostsidetests/appsecurity/test-apps/tinyapp.
assertInstallSucceeds("original.apk");
}
@Test
public void testInstallV1OneSignerMD5withRSA() throws Exception {
// APK signed with v1 scheme only, one signer.
assertInstallSucceedsForEach(
"v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES);
assertInstallSucceedsForEach(
"v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-%s.apk", RSA_KEY_NAMES);
}
@Test
public void testInstallV1OneSignerSHA1withRSA() throws Exception {
// APK signed with v1 scheme only, one signer.
assertInstallSucceedsForEach(
"v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES);
assertInstallSucceedsForEach(
"v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-%s.apk", RSA_KEY_NAMES);
}
@Test
public void testInstallV1OneSignerSHA224withRSA() throws Exception {
// APK signed with v1 scheme only, one signer.
assertInstallSucceedsForEach(
"v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES);
assertInstallSucceedsForEach(
"v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-%s.apk", RSA_KEY_NAMES);
}
@Test
public void testInstallV1OneSignerSHA256withRSA() throws Exception {
// APK signed with v1 scheme only, one signer.
assertInstallSucceedsForEach(
"v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES);
assertInstallSucceedsForEach(
"v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-%s.apk", RSA_KEY_NAMES);
}
@Test
public void testInstallV1OneSignerSHA384withRSA() throws Exception {
// APK signed with v1 scheme only, one signer.
assertInstallSucceedsForEach(
"v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES);
assertInstallSucceedsForEach(
"v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-%s.apk", RSA_KEY_NAMES);
}
@Test
public void testInstallV1OneSignerSHA512withRSA() throws Exception {
// APK signed with v1 scheme only, one signer.
assertInstallSucceedsForEach(
"v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES);
assertInstallSucceedsForEach(
"v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-%s.apk", RSA_KEY_NAMES);
}
@Test
public void testInstallV1OneSignerSHA1withECDSA() throws Exception {
// APK signed with v1 scheme only, one signer.
assertInstallSucceedsForEach(
"v1-only-with-ecdsa-sha1-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES);
assertInstallSucceedsForEach(
"v1-only-with-ecdsa-sha1-1.2.840.10045.4.1-%s.apk", EC_KEY_NAMES);
}
@Test
public void testInstallV1OneSignerSHA224withECDSA() throws Exception {
// APK signed with v1 scheme only, one signer.
assertInstallSucceedsForEach(
"v1-only-with-ecdsa-sha224-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES);
assertInstallSucceedsForEach(
"v1-only-with-ecdsa-sha224-1.2.840.10045.4.3.1-%s.apk", EC_KEY_NAMES);
}
@Test
public void testInstallV1OneSignerSHA256withECDSA() throws Exception {
// APK signed with v1 scheme only, one signer.
assertInstallSucceedsForEach(
"v1-only-with-ecdsa-sha256-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES);
assertInstallSucceedsForEach(
"v1-only-with-ecdsa-sha256-1.2.840.10045.4.3.2-%s.apk", EC_KEY_NAMES);
}
@Test
public void testInstallV1OneSignerSHA384withECDSA() throws Exception {
// APK signed with v1 scheme only, one signer.
assertInstallSucceedsForEach(
"v1-only-with-ecdsa-sha384-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES);
assertInstallSucceedsForEach(
"v1-only-with-ecdsa-sha384-1.2.840.10045.4.3.3-%s.apk", EC_KEY_NAMES);
}
@Test
public void testInstallV1OneSignerSHA512withECDSA() throws Exception {
// APK signed with v1 scheme only, one signer.
assertInstallSucceedsForEach(
"v1-only-with-ecdsa-sha512-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES);
assertInstallSucceedsForEach(
"v1-only-with-ecdsa-sha512-1.2.840.10045.4.3.4-%s.apk", EC_KEY_NAMES);
}
@Test
public void testInstallV1OneSignerSHA1withDSA() throws Exception {
// APK signed with v1 scheme only, one signer.
assertInstallSucceedsForEach(
"v1-only-with-dsa-sha1-1.2.840.10040.4.1-%s.apk", DSA_KEY_NAMES);
assertInstallSucceedsForEach(
"v1-only-with-dsa-sha1-1.2.840.10040.4.3-%s.apk", DSA_KEY_NAMES);
}
@Test
public void testInstallV1OneSignerSHA224withDSA() throws Exception {
// APK signed with v1 scheme only, one signer.
assertInstallSucceedsForEach(
"v1-only-with-dsa-sha224-1.2.840.10040.4.1-%s.apk", DSA_KEY_NAMES);
assertInstallSucceedsForEach(
"v1-only-with-dsa-sha224-2.16.840.1.101.3.4.3.1-%s.apk", DSA_KEY_NAMES);
}
@Test
public void testInstallV1OneSignerSHA256withDSA() throws Exception {
// APK signed with v1 scheme only, one signer.
assertInstallSucceedsForEach(
"v1-only-with-dsa-sha256-1.2.840.10040.4.1-%s.apk", DSA_KEY_NAMES);
assertInstallSucceedsForEach(
"v1-only-with-dsa-sha256-2.16.840.1.101.3.4.3.2-%s.apk", DSA_KEY_NAMES);
}
// Android platform doesn't support DSA with SHA-384 and SHA-512.
// @Test
// public void testInstallV1OneSignerSHA384withDSA() throws Exception {
// // APK signed with v1 scheme only, one signer.
// assertInstallSucceedsForEach(
// "v1-only-with-dsa-sha384-2.16.840.1.101.3.4.3.3-%s.apk", DSA_KEY_NAMES);
// }
//
// @Test
// public void testInstallV1OneSignerSHA512withDSA() throws Exception {
// // APK signed with v1 scheme only, one signer.
// assertInstallSucceedsForEach(
// "v1-only-with-dsa-sha512-2.16.840.1.101.3.4.3.3-%s.apk", DSA_KEY_NAMES);
// }
@Test
public void testInstallV2StrippedFails() 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.
assertInstallFailsWithError("v2-stripped.apk", "Signature stripped");
// 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.
assertInstallFailsWithError(
"v2-stripped-with-ignorable-signing-schemes.apk", "Signature stripped");
}
@Test
public void testInstallV2OneSignerOneSignature() throws Exception {
// APK signed with v2 scheme only, one signer, one signature.
assertInstallSucceedsForEach("v2-only-with-dsa-sha256-%s.apk", DSA_KEY_NAMES);
assertInstallSucceedsForEach("v2-only-with-ecdsa-sha256-%s.apk", EC_KEY_NAMES);
assertInstallSucceedsForEach("v2-only-with-rsa-pkcs1-sha256-%s.apk", RSA_KEY_NAMES);
assertInstallSucceedsForEach("v2-only-with-rsa-pss-sha256-%s.apk", RSA_KEY_NAMES);
// 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);
assertInstallSucceedsForEach("v2-only-with-ecdsa-sha512-%s.apk", EC_KEY_NAMES);
assertInstallSucceedsForEach("v2-only-with-rsa-pkcs1-sha512-%s.apk", RSA_KEY_NAMES);
assertInstallSucceedsForEach(
"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
);
}
@Test
public void testInstallV1SignatureOnlyDoesNotVerify() throws Exception {
// APK signed with v1 scheme only, but not all digests match those recorded in
// META-INF/MANIFEST.MF.
String error = "META-INF/MANIFEST.MF has invalid digest";
// Bitflip in classes.dex of otherwise good file.
assertInstallFailsWithError(
"v1-only-with-tampered-classes-dex.apk", error);
}
@Test
public void testInstallV2SignatureDoesNotVerify() throws Exception {
// APK signed with v2 scheme only, but the signature over signed-data does not verify.
String error = "signature did not verify";
// Bitflip in certificate field inside signed-data. Based on
// v2-only-with-dsa-sha256-1024.apk.
assertInstallFailsWithError("v2-only-with-dsa-sha256-1024-sig-does-not-verify.apk", error);
// 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.
assertInstallFailsWithError(
"v2-only-with-rsa-pkcs1-sha256-2048-sig-does-not-verify.apk", error);
// 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.
assertInstallFailsWithError(
"v2-only-with-rsa-pss-sha256-2048-sig-does-not-verify.apk", error);
// Bitflip in the ECDSA signature. Based on v2-only-with-ecdsa-sha256-p256.apk.
assertInstallFailsWithError(
"v2-only-with-ecdsa-sha256-p256-sig-does-not-verify.apk", error);
}
@Test
public void testInstallV2ContentDigestMismatch() throws Exception {
// APK signed with v2 scheme only, but the digest of contents does not match the digest
// stored in signed-data.
String error = "digest of contents 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.
assertInstallFailsWithError(
"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.
assertInstallFailsWithError(
"v2-only-with-ecdsa-sha256-p256-digest-mismatch.apk", error);
}
@Test
public void testInstallNoApkSignatureSchemeBlock() 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.
assertInstallFails("v2-only-wrong-apk-sig-block-magic.apk");
// 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.
assertInstallFails("v2-only-garbage-between-cd-and-eocd.apk");
// 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
assertInstallFails("v2-only-truncated-cd.apk");
// Obtained by modifying the size in APK Signature Block header. Based on
// v2-only-with-ecdsa-sha512-p521.apk.
assertInstallFails("v2-only-apk-sig-block-size-mismatch.apk");
// 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.
assertInstallSucceeds("v1-with-apk-sig-block-but-without-apk-sig-scheme-v2-block.apk");
}
@Test
public void testInstallV2UnknownPairIgnoredInApkSigningBlock() 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.
assertInstallSucceeds("v2-only-unknown-pair-in-apk-sig-block.apk");
}
@Test
public void testInstallV2IgnoresUnknownSignatureAlgorithms() 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.
assertInstallSucceeds("v2-only-with-ignorable-unsupported-sig-algs.apk");
}
@Test
public void testInstallV2RejectsMismatchBetweenSignaturesAndDigestsBlocks() 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.
assertInstallFailsWithError(
"v2-only-signatures-and-digests-block-mismatch.apk",
"Signature algorithms don't match between digests and signatures records");
}
@Test
public void testInstallV2RejectsMismatchBetweenPublicKeyAndCertificate() 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.
assertInstallFailsWithError(
"v2-only-cert-and-public-key-mismatch.apk",
"Public key mismatch between certificate and signature record");
}
@Test
public void testInstallV2RejectsSignerBlockWithNoCertificates() 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.
assertInstallFailsWithError("v2-only-no-certs-in-sig.apk", "No certificates listed");
}
@Test
public void testInstallTwoSigners() throws Exception {
// APK signed by two different signers.
assertInstallSucceeds("two-signers.apk");
// Because the install attempt below is an update, it also tests that the signing
// certificates exposed by v2 signatures above are the same as the one exposed by v1
// signatures in this APK.
assertInstallSucceeds("v1-only-two-signers.apk");
assertInstallSucceeds("v2-only-two-signers.apk");
}
@Test
public void testInstallNegativeModulus() throws Exception {
// APK signed with a certificate that has a negative RSA modulus.
assertInstallSucceeds("v1-only-negative-modulus.apk");
assertInstallSucceeds("v2-only-negative-modulus.apk");
assertInstallSucceeds("v3-only-negative-modulus.apk");
}
@Test
public void testInstallV2TwoSignersRejectsWhenOneBroken() 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.
assertInstallFailsWithError(
"two-signers-second-signer-v2-broken.apk", "signature did not verify");
}
@Test
public void testInstallV2TwoSignersRejectsWhenOneWithoutSignatures() throws Exception {
// APK v2-signed by two different signers. However, there are no signatures for the second
// signer.
assertInstallFailsWithError(
"v2-only-two-signers-second-signer-no-sig.apk", "No signatures");
}
@Test
public void testInstallV2TwoSignersRejectsWhenOneWithoutSupportedSignatures() throws Exception {
// APK v2-signed by two different signers. However, there are no supported signatures for
// the second signer.
assertInstallFailsWithError(
"v2-only-two-signers-second-signer-no-supported-sig.apk",
"No supported signatures");
}
@Test
public void testInstallV2RejectsWhenMissingCode() throws Exception {
// Obtained by removing classes.dex from original.apk and then signing with v2 only.
// Although this has nothing to do with v2 signature verification, package manager wants
// signature verification / certificate collection to reject APKs with missing code
// (classes.dex) unless requested otherwise.
assertInstallFailsWithError("v2-only-missing-classes.dex.apk", "code is missing");
}
@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.
assertInstallSucceeds("v1-only-pkcs7-cert-bag-first-cert-not-used.apk");
// Check that rsa-1024 was not used as the previously installed package's signing cert.
assertInstallFailsWithError(
"v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-1024.apk",
"signatures do not match");
// Check that rsa-2048 was used as the previously installed package's signing cert.
assertInstallSucceeds("v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-2048.apk");
}
@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.
//
// The signing certs of the two APKs differ only in how the cert's signature is encoded.
// From Android's perspective, these two APKs are signed by different entities and thus
// cannot be used to update one another. If signature verification code re-encodes certs
// into DER, both certs will be exactly the same and Android will accept these APKs as
// updates of each other. This test is thus asserting that the two APKs are not accepted as
// updates of each other.
//
// * v1-only-with-rsa-1024.apk cert's signature is DER-encoded
// * 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.
assertInstallSucceeds("v1-only-with-rsa-1024.apk");
assertInstallFailsWithError(
"v1-only-with-rsa-1024-cert-not-der.apk", "signatures do not match");
uninstallPackage();
assertInstallSucceeds("v1-only-with-rsa-1024-cert-not-der.apk");
assertInstallFailsWithError("v1-only-with-rsa-1024.apk", "signatures do not match");
}
@Test
public void testV2SchemeSignatureCertNotReencoded() throws Exception {
// This test is here to catch something like b/30148997 and b/18228011 happening to the
// handling of APK Signature Scheme v2 signatures by PackageManager. 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.
//
// The signing certs of the two APKs differ only in how the cert's signature is encoded.
// From Android's perspective, these two APKs are signed by different entities and thus
// cannot be used to update one another. If signature verification code re-encodes certs
// into DER, both certs will be exactly the same and Android will accept these APKs as
// updates of each other. This test is thus asserting that the two APKs are not accepted as
// updates of each other.
//
// * v2-only-with-rsa-pkcs1-sha256-1024.apk cert's signature is DER-encoded
// * v2-only-with-rsa-pkcs1-sha256-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.
assertInstallSucceeds("v2-only-with-rsa-pkcs1-sha256-1024.apk");
assertInstallFailsWithError(
"v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk", "signatures do not match");
uninstallPackage();
assertInstallSucceeds("v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk");
assertInstallFailsWithError(
"v2-only-with-rsa-pkcs1-sha256-1024.apk", "signatures do not match");
}
@Test
public void testInstallMaxSizedZipEocdComment() throws Exception {
// Obtained by modifying apksigner to produce a 0xffff-byte long ZIP End of
// Central Directory comment which exceeds the maximum size of comment,
// and signing the original.apk using the modified apksigner.
assertInstallFailsWithError("v1-only-max-sized-eocd-comment.apk", "Unknown failure");
assertInstallFailsWithError("v2-only-max-sized-eocd-comment.apk", "Unknown failure");
}
@Test
public void testInstallEphemeralRequiresV2Signature() throws Exception {
assertInstallEphemeralFailsWithError("unsigned-ephemeral.apk",
"Failed to collect certificates");
assertInstallEphemeralFailsWithError("v1-only-ephemeral.apk",
"must be signed with APK Signature Scheme v2 or greater");
assertInstallEphemeralSucceeds("v2-only-ephemeral.apk");
assertInstallEphemeralSucceeds("v1-v2-ephemeral.apk"); // signed with both schemes
}
@Test
public void testInstallEmpty() throws Exception {
assertInstallFailsWithError("empty-unsigned.apk", "Unknown failure");
assertInstallFailsWithError("v1-only-empty.apk", "Unknown failure");
assertInstallFailsWithError("v2-only-empty.apk", "Unknown failure");
}
@AsbSecurityTest(cveBugId = 64211847)
@Test
public void testInstallApkWhichDoesNotStartWithZipLocalFileHeaderMagic() throws Exception {
// The APKs below are competely fine except they don't start with ZIP Local File Header
// magic. Thus, these APKs will install just fine unless Package Manager requires that APKs
// start with ZIP Local File Header magic.
String error = "Unknown failure";
// Obtained by modifying apksigner to output four unused 0x00 bytes at the start of the APK
assertInstallFailsWithError("v1-only-starts-with-00000000-magic.apk", error);
assertInstallFailsWithError("v2-only-starts-with-00000000-magic.apk", error);
// Obtained by modifying apksigner to output 8 unused bytes (DEX magic and version) at the
// start of the APK
assertInstallFailsWithError("v1-only-starts-with-dex-magic.apk", error);
assertInstallFailsWithError("v2-only-starts-with-dex-magic.apk", error);
}
@Test
public void testInstallV3KeyRotation() throws Exception {
// tests that a v3 signed APK with RSA key can rotate to a new key
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1.apk");
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps.apk");
}
@Test
public void testInstallV3KeyRotationToAncestor() throws Exception {
// tests that a v3 signed APK with RSA key cannot be upgraded by one of its past certs
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps.apk");
assertInstallFails("v3-rsa-pkcs1-sha256-2048-1.apk");
}
@Test
public void testInstallV3KeyRotationToAncestorWithRollback() throws Exception {
// tests that a v3 signed APK with RSA key can be upgraded by one of its past certs if it
// has granted that cert the rollback capability
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-and-roll-caps.apk");
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1.apk");
}
@Test
public void testInstallV3KeyRotationMultipleHops() throws Exception {
// tests that a v3 signed APK with RSA key can rotate to a new key which is the result of
// multiple rotations from the original: APK signed with key 1 can be updated by key 3, when
// keys were: 1 -> 2 -> 3
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1.apk");
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-3-with-por_1_2_3-full-caps.apk");
}
@Test
public void testInstallV3PorSignerMismatch() throws Exception {
// tests that an APK with a proof-of-rotation struct that doesn't include the current
// signing certificate fails to install
assertInstallFails("v3-rsa-pkcs1-sha256-2048-3-with-por_1_2-full-caps.apk");
}
@Test
public void testInstallV3KeyRotationWrongPor() throws Exception {
// tests that a valid APK with a proof-of-rotation record can't upgrade an APK with a
// signing certificate that isn't in the proof-of-rotation record
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1.apk");
assertInstallFails("v3-rsa-pkcs1-sha256-2048-3-with-por_2_3-full-caps.apk");
}
@Test
public void testInstallV3KeyRotationSharedUid() throws Exception {
// tests that a v3 signed sharedUid APK can still be sharedUid with apps with its older
// signing certificate, if it so desires
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1-sharedUid.apk");
assertInstallSucceeds(
"v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps-sharedUid-companion.apk");
}
@Test
public void testInstallV3KeyRotationOlderSharedUid() throws Exception {
// tests that a sharedUid APK can still install with another app that is signed by a newer
// signing certificate, but which allows sharedUid with the older one
assertInstallSucceeds(
"v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps-sharedUid-companion.apk");
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1-sharedUid.apk");
}
@Test
public void testInstallV3KeyRotationSharedUidNoCap() throws Exception {
// tests that a v3 signed sharedUid APK cannot be sharedUid with apps with its older
// signing certificate, when it has not granted that certificate the sharedUid capability
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1-sharedUid.apk");
assertInstallFails(
"v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-no-shUid-cap-sharedUid-companion.apk");
}
@Test
public void testInstallV3KeyRotationOlderSharedUidNoCap() throws Exception {
// tests that a sharedUid APK signed with an old certificate cannot install with
// an app having a proof-of-rotation structure that hasn't granted the older
// certificate the sharedUid capability
assertInstallSucceeds(
"v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-no-shUid-cap-sharedUid-companion.apk");
assertInstallFails("v3-rsa-pkcs1-sha256-2048-1-sharedUid.apk");
}
@Test
public void testInstallV3NoRotationSharedUid() throws Exception {
// tests that a sharedUid APK signed with a new certificate installs with
// an app having a proof-of-rotation structure that hasn't granted an older
// certificate the sharedUid capability
assertInstallSucceeds(
"v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-no-shUid-cap-sharedUid-companion.apk");
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-sharedUid.apk");
}
@Test
public void testInstallV3MultipleAppsOneDeniesOldKeySharedUid() throws Exception {
// If two apps are installed as part of a sharedUid, one granting access to the sharedUid
// to the previous key and the other revoking access to the sharedUid, then when an app
// signed with the old key attempts to join the sharedUid the installation should be blocked
assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-no-shUid-cap-sharedUid.apk");
assertInstallFromBuildSucceeds(
"v3-ec-p256-with-por_1_2-default-caps-sharedUid-companion.apk");
assertInstallFromBuildFails("v3-ec-p256-1-sharedUid-companion2.apk");
}
@Test
public void testInstallV3MultipleAppsOneUpdatedToDenyOldKeySharedUid() throws Exception {
// Similar to the test above if two apps are installed as part of a sharedUid with both
// granting access to the sharedUid to the previous key then an app signed with the previous
// key should be allowed to install and join the sharedUid. If one of the first two apps
// is then updated with a lineage that denies access to the sharedUid for the old key, all
// subsequent installs / updates with that old key should be blocked.
assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-default-caps-sharedUid.apk");
assertInstallFromBuildSucceeds(
"v3-ec-p256-with-por_1_2-default-caps-sharedUid-companion.apk");
assertInstallFromBuildSucceeds("v3-ec-p256-1-sharedUid-companion2.apk");
assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-no-shUid-cap-sharedUid.apk");
assertInstallFromBuildFails("v3-ec-p256-1-sharedUid-companion2.apk");
}
@Test
public void testInstallV3SharedUidDeniedOnlyRotatedUpdateAllowed() throws Exception {
// To allow rotation after a signing key compromise, an APK that is already part of a
// shareddUserId can rotate to a new key with the old key being denied the SHARED_USER_ID
// capability and still be updated in the sharedUserId. Another app signed with this same
// lineage and capabilities that is not currently part of the sharedUserId will not be
// allowed to join as long as any apps signed with the untrusted key are still part of
// the sharedUserId.
assertInstallFromBuildSucceeds("v3-ec-p256-1-sharedUid.apk");
assertInstallFromBuildSucceeds("v3-ec-p256-1-sharedUid-companion2.apk");
assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-no-shUid-cap-sharedUid.apk");
// An app signed with the untrusted key is still part of the sharedUserId, so a new app
// that does not trust this key is not allowed to join the sharedUserId.
assertInstallFromBuildFails("v3-ec-p256-with-por_1_2-no-shUid-cap-sharedUid-companion.apk");
assertInstallFromBuildSucceeds(
"v3-ec-p256-with-por_1_2-no-shUid-cap-sharedUid-companion2.apk");
// Once all apps have rotated away from the untrusted key, a new app that also does not
// trust the previous key can now join the sharedUserId.
assertInstallFromBuildSucceeds(
"v3-ec-p256-with-por_1_2-no-shUid-cap-sharedUid-companion.apk");
}
@Test
public void testInstallV3FirstAppOnlySignedByNewKeyLastAppOldKey() throws Exception {
// This test verifies the following scenario:
// - First installed app in sharedUid only signed with new key without lineage.
// - Second installed app in sharedUid signed with new key and includes lineage granting
// access to the old key to join the sharedUid.
// - Last installed app in sharedUid signed with old key.
// The lineage should be updated when the second app is installed to allow the installation
// of the app signed with the old key.
assertInstallFromBuildSucceeds("v3-ec-p256-2-sharedUid-companion.apk");
assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-default-caps-sharedUid.apk");
assertInstallFromBuildSucceeds("v3-ec-p256-1-sharedUid-companion2.apk");
}
@Test
public void testInstallV3AppSignedWithOldKeyUpdatedLineageDeniesShUidCap() throws Exception {
// If an app is installed as part of a sharedUid, and then that app is signed with a new key
// that rejects the previous key in the lineage the update should be allowed to proceed
// as the app is being updated to the newly rotated key.
assertInstallFromBuildSucceeds("v3-ec-p256-1-sharedUid.apk");
assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-no-shUid-cap-sharedUid.apk");
}
@Test
public void testInstallV3TwoSharedUidAppsWithDivergedLineages() throws Exception {
// Apps that are installed as part of the sharedUserId with a lineage must have common
// ancestors; the platform will allow the installation if the lineage of an app being
// installed as part of the sharedUserId is the same, a subset, or a superset of the
// existing lineage, but if the lineage diverges then the installation should be blocked.
assertInstallFromBuildSucceeds("v3-por_Y_1_2-default-caps-sharedUid.apk");
assertInstallFromBuildFails("v3-por_Z_1_2-default-caps-sharedUid-companion.apk");
}
@Test
public void testInstallV3WithRestoredCapabilityInSharedUserId() throws Exception {
// A sharedUserId contains the shared signing lineage for all packages in the UID; this
// shared lineage contain the full signing history for all packages along with the merged
// capabilities for each signer shared between the packages. This test verifies if one
// package revokes a capability from a previous signer, but subsequently restores that
// capability, then since all packages have granted the capability, it is restored to the
// previous signer in the shared lineage.
// Install a package with the SHARED_USER_ID capability revoked for the original signer
// in the lineage; verify that a package signed with only the original signer cannot join
// the sharedUserId.
assertInstallFromBuildSucceeds(
"v3-ec-p256-with-por_1_2-default-caps-sharedUid-companion.apk");
assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-no-shUid-cap-sharedUid.apk");
assertInstallFromBuildFails("v3-ec-p256-1-sharedUid-companion2.apk");
// Update the package that revoked the SHARED_USER_ID with an updated lineage that restores
// this capability to the original signer; verify the package signed with the original
// signing key can now join the sharedUserId since all existing packages in the UID grant
// this capability to the original signer.
assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-default-caps-sharedUid.apk");
assertInstallFromBuildSucceeds("v3-ec-p256-1-sharedUid-companion2.apk");
}
@Test
public void testInstallV3WithRevokedCapabilityInSharedUserId() throws Exception {
// While a capability can be restored to a common signer in the shared signing lineage, if
// one package has revoked a capability from a common signer and another package is
// installed / updated which restores the capability to that signer, the revocation of
// the capability by the existing package should take precedence. A capability can only
// be restored to a common signer if all packages in the sharedUserId have granted this
// capability to the signer.
// Install a package with the SHARED_USER_ID capability revoked from the original signer,
// then install another package in the sharedUserId that grants this capability to the
// original signer. Since a package exists in the sharedUserId that has revoked this
// capability, another package signed with this capability shouldn't be able to join the
// sharedUserId.
assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-no-shUid-cap-sharedUid.apk");
assertInstallFromBuildSucceeds(
"v3-ec-p256-with-por_1_2-default-caps-sharedUid-companion.apk");
assertInstallFromBuildFails("v3-ec-p256-1-sharedUid-companion2.apk");
// Install the same package that grants the SHARED_USER_ID capability to the original
// signer; when iterating over the existing packages in the packages in the sharedUserId,
// the original version of this package should be skipped since the lineage from the
// updated package is used when merging with the shared lineage.
assertInstallFromBuildSucceeds(
"v3-ec-p256-with-por_1_2-default-caps-sharedUid-companion.apk");
assertInstallFromBuildFails("v3-ec-p256-1-sharedUid-companion2.apk");
// Install another package that has granted the SHARED_USER_ID to the original signer; this
// should trigger another merge with all packages in the sharedUserId. Since one still
// remains that revokes the capability, the capability should be revoked in the shared
// lineage.
assertInstallFromBuildSucceeds(
"v3-ec-p256-with-por_1_2-default-caps-sharedUid-companion3.apk");
assertInstallFromBuildFails("v3-ec-p256-1-sharedUid-companion2.apk");
}
@Test
public void testInstallV3UpdateAfterRotation() throws Exception {
// This test performs an end to end verification of the update of an app with a rotated
// key. The app under test exports a bound service that performs its own PackageManager key
// rotation API verification, and the instrumentation test binds to the service and invokes
// the verifySignatures method to verify that the key rotation APIs return the expected
// results. The instrumentation test app is signed with the same key and lineage as the
// app under test to also provide a second app that can be used for the checkSignatures
// verification.
// Install the initial versions of the apps; the test method verifies the app under test is
// signed with the original signing key.
assertInstallFromBuildSucceeds("CtsSignatureQueryService.apk");
assertInstallFromBuildSucceeds("CtsSignatureQueryServiceTest.apk");
Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
"verifySignatures_noRotation_succeeds");
// Install the second version of the app signed with the rotated key. This test verifies the
// app still functions as expected after the update with the rotated key. The
// instrumentation test app is not updated here to allow verification of the pre-key
// rotation behavior for the checkSignatures APIs. These APIs should behave similar to the
// GET_SIGNATURES flag in that if one or both apps have a signing lineage if the oldest
// signers in the lineage match then the methods should return that the signatures match
// even if one is signed with a newer key in the lineage.
assertInstallFromBuildSucceeds("CtsSignatureQueryService_v2.apk");
Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
"verifySignatures_withRotation_succeeds");
// Installs the third version of the app under test and the instrumentation test, both
// signed with the same rotated key and lineage. This test is intended to verify that the
// app can still be updated and function as expected after an update with a rotated key.
assertInstallFromBuildSucceeds("CtsSignatureQueryService_v3.apk");
assertInstallFromBuildSucceeds("CtsSignatureQueryServiceTest_v2.apk");
Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
"verifySignatures_withRotation_succeeds");
}
@CddTest(requirement="4/C-0-2")
@Test
public void testInstallV31UpdateAfterRotation() throws Exception {
// This test is the same as above, but using the v3.1 signature scheme for rotation.
assertInstallFromBuildSucceeds("CtsSignatureQueryService.apk");
assertInstallFromBuildSucceeds("CtsSignatureQueryServiceTest.apk");
Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
"verifySignatures_noRotation_succeeds");
assertInstallFromBuildSucceeds("CtsSignatureQueryService_v2-tgt-33.apk");
Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
"verifySignatures_withRotation_succeeds");
assertInstallFromBuildSucceeds("CtsSignatureQueryService_v3-tgt-33.apk");
assertInstallFromBuildSucceeds("CtsSignatureQueryServiceTest_v2-tgt-33.apk");
Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
"verifySignatures_withRotation_succeeds");
}
@CddTest(requirement="4/C-0-9")
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV41UpdateAfterRotation(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// This test is the same as above, but using the v4.1 signature scheme for rotation.
assertInstallV4FromBuildSucceeds("CtsSignatureQueryService.apk");
assertInstallV4FromBuildSucceeds("CtsSignatureQueryServiceTest.apk");
Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
"verifySignatures_noRotation_succeeds");
assertInstallV4FromBuildSucceeds("CtsSignatureQueryService_v2-tgt-33.apk");
Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
"verifySignatures_withRotation_succeeds");
assertInstallV4FromBuildSucceeds("CtsSignatureQueryService_v3-tgt-33.apk");
assertInstallV4FromBuildSucceeds("CtsSignatureQueryServiceTest_v2-tgt-33.apk");
Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
"verifySignatures_withRotation_succeeds");
}
@CddTest(requirement="4/C-0-9")
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV41WrongBlockId(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// This test is the same as above, but using the v4.1 signature scheme for rotation.
assertInstallV4FromBuildSucceeds("CtsSignatureQueryService.apk");
assertInstallV4FromBuildSucceeds("CtsSignatureQueryServiceTest.apk");
Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
"verifySignatures_noRotation_succeeds");
assertInstallV4FailsWithError("CtsSignatureQueryService_v2-tgt-33-wrongV41Block.apk",
"Failed to find V4 signature block corresponding to V3 blockId: 462663009");
}
@CddTest(requirement="4/C-0-9")
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV41LegacyV4(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// This test is the same as above, but using the v4.1 signature scheme for rotation.
assertInstallV4FromBuildSucceeds("CtsSignatureQueryService.apk");
assertInstallV4FromBuildSucceeds("CtsSignatureQueryServiceTest.apk");
Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
"verifySignatures_noRotation_succeeds");
assertInstallV4FailsWithError("CtsSignatureQueryService_v2-tgt-33-legacyV4.apk",
"Failed to find V4 signature block corresponding to V3 blockId: 462663009");
}
@CddTest(requirement="4/C-0-9")
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV41WrongDigest(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// This test is the same as above, but using the v4.1 signature scheme for rotation.
assertInstallV4FromBuildSucceeds("CtsSignatureQueryService.apk");
assertInstallV4FromBuildSucceeds("CtsSignatureQueryServiceTest.apk");
Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
"verifySignatures_noRotation_succeeds");
assertInstallV4FailsWithError("CtsSignatureQueryService_v2-tgt-33-wrongDigest.apk",
"APK digest in V4 signature does not match V2/V3");
}
@Test
public void testInstallV3KeyRotationSigPerm() throws Exception {
// tests that a v3 signed APK can still get a signature permission from an app with its
// older signing certificate.
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1-permdef.apk");
assertInstallSucceeds(
"v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps-permcli-companion.apk");
Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasPerm");
}
@Test
public void testInstallV3KeyRotationOlderSigPerm() throws Exception {
// tests that an apk with an older signing certificate than the one which defines a
// signature permission it wants gets the permission if the defining APK grants the
// capability
assertInstallSucceeds(
"v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps-permdef.apk");
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1-permcli-companion.apk");
Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasPerm");
}
@Test
public void testInstallV3KeyRotationSigPermNoCap() throws Exception {
// tests that an APK signed by an older signing certificate is unable to get a requested
// signature permission when the defining APK has rotated to a newer signing certificiate
// and does not grant the permission capability to the older cert
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-no-perm-cap-permdef.apk");
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1-permcli-companion.apk");
Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasNoPerm");
}
@Test
public void testInstallV3KeyRotationOlderSigPermNoCap() throws Exception {
// tests that an APK signed by a newer signing certificate than the APK which defines a
// signature permission is able to get that permission, even if the newer APK does not
// grant the permission capability to the older signing certificate.
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1-permdef.apk");
assertInstallSucceeds(
"v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-no-perm-cap-permcli-companion.apk");
Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasPerm");
}
@Test
public void testInstallV3NoRotationSigPerm() throws Exception {
// make sure that an APK, which wants to use a signature permission defined by an APK, which
// has not granted that capability to older signing certificates, can still install
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-no-perm-cap-permdef.apk");
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-permcli-companion.apk");
Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasPerm");
}
@Test
public void testInstallV3CommonSignerInLineageWithPermCap() throws Exception {
// If an APK requesting a signature permission has a common signer in the lineage with the
// APK declaring the permission, and that signer is granted the permission capability in
// the declaring APK, then the permission should be granted to the requesting app even
// if their signers have diverged.
assertInstallFromBuildSucceeds(
"v3-ec-p256-with-por_1_2_3-1-no-caps-2-default-declperm.apk");
assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2_4-companion-usesperm.apk");
Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasPerm");
}
@Test
public void testInstallV3CommonSignerInLineageNoCaps() throws Exception {
// If an APK requesting a signature permission has a common signer in the lineage with the
// APK declaring the permission, but the signer in the lineage has not been granted the
// permission capability the permission should not be granted to the requesting app.
assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2_3-no-caps-declperm.apk");
assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2_4-companion-usesperm.apk");
Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasNoPerm");
}
@Test
public void testKnownSignerPermGrantedWhenCurrentSignerInResource() throws Exception {
// The knownSigner protection flag allows an app to declare other trusted signing
// certificates in an array resource; if a requesting app's current signer is in this array
// of trusted certificates then the permission should be granted.
assertInstallFromBuildSucceeds("v3-rsa-2048-decl-knownSigner-ec-p256-1-3.apk");
assertInstallFromBuildSucceeds("v3-ec-p256_3-companion-uses-knownSigner.apk");
Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasPerm");
// If the declaring app changes the trusted certificates on an update any requesting app
// that no longer meets the requirements based on its signing identity should have the
// permission revoked. This app update only trusts ec-p256_1 but the app that was previously
// granted the permission based on its signing identity is signed by ec-p256_3.
assertInstallFromBuildSucceeds("v3-rsa-2048-decl-knownSigner-str-res-ec-p256-1.apk");
Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasNoPerm");
}
@Test
public void testKnownSignerPermCurrentSignerNotInResource() throws Exception {
// If an app requesting a knownSigner permission does not meet the requirements for a
// signature permission and is not signed by any of the trusted certificates then the
// permission should not be granted.
assertInstallFromBuildSucceeds("v3-rsa-2048-decl-knownSigner-ec-p256-1-3.apk");
assertInstallFromBuildSucceeds("v3-ec-p256_2-companion-uses-knownSigner.apk");
Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasNoPerm");
}
@Test
public void testKnownSignerPermGrantedWhenSignerInLineageInResource() throws Exception {
// If an app requesting a knownSigner permission was previously signed by a certificate
// that is trusted by the declaring app then the permission should be granted.
assertInstallFromBuildSucceeds("v3-rsa-2048-decl-knownSigner-ec-p256-1-3.apk");
assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-companion-uses-knownSigner.apk");
Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasPerm");
// If the declaring app changes the permission to no longer use the knownSigner flag then
// any app granted the permission based on a signing identity from the set of trusted
// certificates should have the permission revoked.
assertInstallFromBuildSucceeds("v3-rsa-2048-declperm.apk");
Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasNoPerm");
}
@Test
public void testKnownSignerPermSignerInLineageMatchesStringResource() throws Exception {
// The knownSigner protection flag allows an app to declare a single known trusted
// certificate digest using a string resource instead of a string-array resource. This test
// verifies the knownSigner permission is granted to a requesting app if the single trusted
// cert is in the requesting app's lineage.
assertInstallFromBuildSucceeds("v3-rsa-2048-decl-knownSigner-str-res-ec-p256-1.apk");
assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-companion-uses-knownSigner.apk");
Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasPerm");
}
@Test
public void testKnownSignerPermSignerInLineageMatchesStringConst() throws Exception {
// The knownSigner protection flag allows an app to declare a single known trusted
// certificate digest using a string constant as the knownCerts attribute value instead of a
// resource. This test verifies the knownSigner permission is granted to a requesting app if
// the single trusted cert is in the requesting app's lineage.
assertInstallFromBuildSucceeds("v3-rsa-2048-decl-knownSigner-str-const-ec-p256-1.apk");
assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-companion-uses-knownSigner.apk");
Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasPerm");
}
@Test
public void testInstallV3SigPermDoubleDefNewerSucceeds() throws Exception {
// make sure that if an app defines a signature permission already defined by another app,
// it successfully installs if the other app's signing cert is in its past signing certs and
// the signature permission capability is granted
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1-permdef.apk");
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with_por_1_2-permdef-companion.apk");
}
@Test
public void testInstallV3SigPermDoubleDefOlderSucceeds() throws Exception {
// make sure that if an app defines a signature permission already defined by another app,
// it successfully installs if it is in the other app's past signing certs and the signature
// permission capability is granted
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with_por_1_2-permdef-companion.apk");
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1-permdef.apk");
}
@Test
public void testInstallV3SigPermDoubleDefNewerNoCapFails() throws Exception {
// make sure that if an app defines a signature permission already defined by another app,
// it fails to install if the other app's signing cert is in its past signing certs but the
// signature permission capability is not granted
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1-permdef.apk");
assertInstallFails(
"v3-rsa-pkcs1-sha256-2048-2-with_por_1_2-no-perm-cap-permdef-companion.apk");
}
@Test
public void testInstallV3SigPermDoubleDefOlderNoCapFails() throws Exception {
// make sure that if an app defines a signature permission already defined by another app,
// it fails to install if it is in the other app's past signing certs but the signature
// permission capability is not granted
assertInstallSucceeds(
"v3-rsa-pkcs1-sha256-2048-2-with_por_1_2-no-perm-cap-permdef-companion.apk");
assertInstallFails("v3-rsa-pkcs1-sha256-2048-1-permdef.apk");
}
@Test
public void testInstallV3SigPermDoubleDefSameNoCapSucceeds() throws Exception {
// make sure that if an app defines a signature permission already defined by another app,
// it installs successfully when signed by the same certificate, even if the original app
// does not grant signature capabilities to its past certs
assertInstallSucceeds(
"v3-rsa-pkcs1-sha256-2048-2-with_por_1_2-no-perm-cap-permdef-companion.apk");
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-permdef.apk");
}
@Test
public void testInstallV3KeyRotationGetSignatures() throws Exception {
// tests that a PackageInfo w/GET_SIGNATURES flag returns the older cert
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps.apk");
Utils.runDeviceTests(
getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testGetSignaturesShowsOld");
}
@Test
public void testInstallV3KeyRotationGetSigningCertificates() throws Exception {
// tests that a PackageInfo w/GET_SIGNING_CERTIFICATES flag returns the old and new certs
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps.apk");
Utils.runDeviceTests(
getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
"testGetSigningCertificatesShowsAll");
}
@Test
public void testInstallV3KeyRotationGetApkContentsSigners() throws Exception {
// The GET_SIGNING_CERTIFICATES flag results in a PackageInfo object returned with a
// SigningInfo instance that can be used to query all certificates in the lineage or only
// the current signer(s) via getApkContentsSigners. This test verifies when a V3 signed
// package with a rotated key is queried getApkContentsSigners only returns the current
// signer.
assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-default-caps.apk");
Utils.runDeviceTests(
getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
"testGetApkContentsSignersShowsCurrent");
}
@Test
public void testInstallV2MultipleSignersGetApkContentsSigners() throws Exception {
// Similar to the above test, but verifies when an APK is signed with two V2 signers
// getApkContentsSigners returns both of the V2 signers.
assertInstallFromBuildSucceeds("v1v2-ec-p256-two-signers-targetSdk-30.apk");
Utils.runDeviceTests(
getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
"testGetApkContentsSignersShowsMultipleSigners");
}
@Test
public void testInstallV3MultipleSignersInLineageGetSigningCertificateHistory()
throws Exception {
// The APK used for this test is signed with a lineage containing 5 keys in the signing
// history; this test verifies SigningInfo#getSigningCertificateHistory returns all of an
// APKs signers in their order of rotation.
assertInstallFromBuildSucceeds("v3-ec-p256-with-por-1_2_3_4_5-default-caps.apk");
Utils.runDeviceTests(
getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
"testGetSigningCertificateHistoryReturnsSignersInOrder");
}
@Test
public void testInstallV3KeyRotationHasSigningCertificate() throws Exception {
// tests that hasSigningCertificate() recognizes past and current signing certs
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps.apk");
Utils.runDeviceTests(
getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
"testHasSigningCertificate");
}
@Test
public void testInstallV3KeyRotationHasSigningCertificateSha256() throws Exception {
// tests that hasSigningCertificate() recognizes past and current signing certs by sha256
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps.apk");
Utils.runDeviceTests(
getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
"testHasSigningCertificateSha256");
}
@Test
public void testInstallV3KeyRotationHasSigningCertificateByUid() throws Exception {
// tests that hasSigningCertificate() recognizes past and current signing certs by uid
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps.apk");
Utils.runDeviceTests(
getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
"testHasSigningCertificateByUid");
}
@Test
public void testInstallV3KeyRotationHasSigningCertificateByUidSha256() throws Exception {
// tests that hasSigningCertificate() recognizes past and current signing certs by uid
// and sha256
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-2-with-por_1_2-full-caps.apk");
Utils.runDeviceTests(
getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
"testHasSigningCertificateByUidSha256");
}
@Test
public void testInstallV3KeyRotationHasDuplicateSigningCertificateHistory() throws Exception {
// tests that an app's proof-of-rotation signing history cannot contain the same certificate
// more than once.
assertInstallFails("v3-rsa-pkcs1-sha256-2048-2-with-por_1_2_2-full-caps.apk");
}
@Test
public void testInstallV3HasMultipleSigners() throws Exception {
// tests that an app can't be signed by multiple signers when using v3 signature scheme
assertInstallFails("v3-rsa-pkcs1-sha256-2048-1_and_2.apk");
}
@Test
public void testInstallV3HasMultiplePlatformSigners() throws Exception {
// tests that an app can be signed by multiple v3 signers if they target different platform
// versions
assertInstallSucceeds("v3-rsa-pkcs1-sha256-2048-1_P_and_2_Qplus.apk");
}
@Test
public void testSharedKeyInSeparateLineageRetainsDeclaredCapabilities() throws Exception {
// This test verifies when a key is used in the signing lineage of multiple apps each
// instance of the key retains its declared capabilities.
// This app has granted the PERMISSION capability to the previous signer in the lineage
// but has revoked the SHARED_USER_ID capability.
assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-no-shUid-cap-declperm2.apk");
// This app has granted the SHARED_USER_ID capability to the previous signer in the lineage
// but has revoked the PERMISSION capability.
assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-no-perm-cap-sharedUid.apk");
// Reboot the device to ensure that the capabilities written to packages.xml are properly
// assigned to the packages installed above; it's possible immediately after a package
// install the capabilities are as declared, but then during the reboot shared signing
// keys also share the initial declared capabilities.
getDevice().reboot();
// This app is signed with the original shared signing key in the lineage and is part of the
// sharedUserId; since the other app in this sharedUserId has granted the required
// capability in the lineage the install should succeed.
assertInstallFromBuildSucceeds("v3-ec-p256-1-sharedUid-companion2.apk");
// This app is signed with the original shared signing key in the lineage and requests the
// signature permission declared by the test app above. Since that app granted the
// PERMISSION capability to the previous signer in the lineage this app should have the
// permission granted.
assertInstallFromBuildSucceeds("v3-ec-p256-1-companion-usesperm.apk");
Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasPerm");
}
@CddTest(requirement="4/C-0-2")
@Test
public void testV31TargetTPlatformUsesRotatedKey() throws Exception {
// The v3.1 signature block is intended to allow applications to target T+ for APK signing
// key rotation without needing multi-targeting APKs. This test verifies a standard APK
// install with the rotated key in the v3.1 signing block targeting T is recognized by the
// platform, and this rotated key is used as the signing identity.
assertInstallSucceeds("v31-ec-p256_2-tgt-33.apk");
Utils.runDeviceTests(
getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
"testUsingRotatedSigner");
}
@CddTest(requirement="4/C-0-2")
@Test
public void testV31TargetLaterThanDevicePlatformUsesOriginalKey() throws Exception {
// The v3.1 signature block allows targeting SDK versions later than T for rotation; for
// this test a target of 100001 is used assuming it will be beyond the platform's version.
// Since the target version for rotation is beyond the platform's version the original
// signer from the v3.0 block should be used.
assertInstallSucceeds("v31-ec-p256_2-tgt-100001.apk");
Utils.runDeviceTests(
getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
"testUsingOriginalSigner");
}
@CddTest(requirement="4/C-0-2")
@Test
public void testV31SignersTargetPAnd100001PlatformUsesTargetPSigner() throws Exception {
// The v3.1 signature scheme allows signer configs to target SDK versions; if a rotated
// signer config is targeting P, the v3.0 block will include a signature with that rotated
// config. This test verifies when the v3.1 signer is targeting an SDK version beyond that
// of the platform's, the rotated signing config from the v3.0 block is used by the
// platform.
assertInstallSucceeds("v31-ec-p256_2-tgt-28-ec-p256_3-tgt-100001.apk");
Utils.runDeviceTests(
getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
"testUsingRotatedSigner");
}
@CddTest(requirement="4/C-0-2")
@Test
public void testV31BlockStrippedWithV3StrippingProtectionAttrSet() throws Exception {
// With the introduction of the v3.1 signature scheme, a new stripping protection attribute
// has been added to the v3.0 signer to protect against stripping and modification of the
// v3.1 signing block. This test verifies a stripped v3.1 block is detected when the v3.0
// stripping protection attribute is set.
assertInstallFails("v31-block-stripped-v3-attr-value-33.apk");
}
@CddTest(requirement="4/C-0-2")
@Test
public void testV31BlockWithMultipleSignersUsesCorrectSigner() throws Exception {
// All of the APKs for this test use multiple v3.1 signers; those targeting SDK versions
// expected to be outside the version of a device under test use the original signer, and
// those targeting an expected range for a device use the rotated key. This test is
// intended to ensure the signer with the min / max SDK version that matches the device
// SDK version is used.
// The APK used for this test contains two signers in the v3.1 signing block. The first
// has a range from 100001 to Integer.MAX_VALUE and is using the original signing key;
// the second targets 33 to 100000 using the rotated key.
assertInstallSucceeds("v31-ec-p256_2-tgt-33-ec-p256-tgt-100001.apk");
Utils.runDeviceTests(
getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
"testUsingRotatedSigner");
uninstallPackage();
// The APK for this test contains two signers in the v3.1 block, one targeting SDK versions
// 1 to 27 using the original signer, and the other targeting 33+ using the rotated signer.
assertInstallSucceeds("v31-ec-p256_2-tgt-33-ec-p256-tgt-1-27.apk");
Utils.runDeviceTests(
getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
"testUsingRotatedSigner");
uninstallPackage();
// This APK combines the extra signers from the APKs above, one targeting 1 to 27 with the
// original signing key, another targeting 100001+ with the original signing key, and the
// last targeting 33 to 100000 with the rotated key.
assertInstallSucceeds("v31-ec-p256_2-tgt-33-ec-p256-tgt-1-27-and-100001.apk");
Utils.runDeviceTests(
getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
"testUsingRotatedSigner");
}
@CddTest(requirement="4/C-0-2")
@Test
public void testV31UpdateV3ToFromV31Succeeds() throws Exception {
// Since the v3.1 block is just intended to allow targeting SDK versions T and later for
// rotation, an APK signed with the rotated key in a v3.0 signing block should support
// updates to an APK signed with the same signing key in a v3.1 signing block.
assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-default-caps.apk");
assertInstallSucceeds("v31-ec-p256_2-tgt-33.apk");
uninstallPackage();
// Similarly an APK signed with the rotated key in a v3.1 signing block should support
// updates to an APK signed with the same signing key in a v3.0 signing block.
assertInstallSucceeds("v31-ec-p256_2-tgt-33.apk");
assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-default-caps.apk");
uninstallPackage();
}
@CddTest(requirement="4/C-0-2")
@Test
public void testV31RotationTargetModifiedReportedByV3() throws Exception {
// When determining if a signer in the v3.1 signing block should be applied, the min / max
// SDK versions from the signer are compared against the device's SDK version; if the device
// is not within the signer's range then the block is skipped, other v3.1 blocks are
// checked, and finally the v3.0 block is used. The v3.0 signer block contains an additional
// attribute with the rotation-min-sdk-version that was expected in the v3.1 signing
// block; if this attribute's value does not match what was found in the v3.1 block the
// APK should fail to install.
assertInstallFails("v31-ec-p256_2-tgt-33-modified.apk");
}
@CddTest(requirement="4/C-0-2")
@Test
public void testV31RotationTargetsDevRelease() throws Exception {
// The v3.1 signature scheme allows targeting a platform release under development through
// the use of a rotation-targets-dev-release additional attribute. Since a platform under
// development shares the same SDK version as the most recently released platform, the
// attribute is used by the platform to determine if a signer block should be applied. If
// the signer's minSdkVersion is the same as the device's SDK version and this attribute
// is set, then the platform will check the value of ro.build.version.codename; a value of
// "REL" indicates the platform is a release platform, so the current signer block will not
// be used. During T's development, the SDK version is 31 and the codename is not "REL", so
// this test APK will install on T during development as well as after its release since
// the SDK version will be bumped at that point.
assertInstallSucceeds("v31-ec-p256_2-tgt-31-dev-release.apk");
}
@Test
public void testInstallTargetSdk30WithV1Signers() throws Exception {
// An app targeting SDK version >= 30 must have at least a V2 signature; this test verifies
// an app targeting SDK version 30 with only a V1 signature fails to install.
assertInstallFails("v1-ec-p256-two-signers-targetSdk-30.apk");
}
@Test
public void testInstallTargetSdk30WithV1V2Signers() throws Exception {
// An app targeting SDK version >= 30 must have at least a V2 signature; this test verifies
// that an app targeting SDK version 30 with both a V1 and V2 signature installs
// successfully.
installApkFromBuild("v1v2-ec-p256-two-signers-targetSdk-30.apk");
}
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV4WithV2Signer(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// APK generated with:
// apksigner sign --v2-signing-enabled true --v3-signing-enabled false --v4-signing-enabled
assertInstallV4Succeeds("v4-digest-v2.apk");
}
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV4WithV3Signer(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// APK generated with:
// apksigner sign --v2-signing-enabled false --v3-signing-enabled true --v4-signing-enabled
assertInstallV4Succeeds("v4-digest-v3.apk");
}
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV4WithV2V3Signer(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// APK generated with:
// apksigner sign --v2-signing-enabled true --v3-signing-enabled true --v4-signing-enabled
assertInstallV4Succeeds("v4-digest-v2v3.apk");
}
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV4WithV2NoVeritySigner(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// APK generated with:
// --v2-signing-enabled true --v3-signing-enabled false --v4-signing-enabled
// Full commands in generate-apks.sh
assertInstallV4SucceedsAndUninstall("v4-digest-v2-Sha256withDSA.apk");
assertInstallV4SucceedsAndUninstall("v4-digest-v2-Sha256withEC.apk");
assertInstallV4SucceedsAndUninstall("v4-digest-v2-Sha256withRSA.apk");
assertInstallV4SucceedsAndUninstall("v4-digest-v2-Sha512withEC.apk");
assertInstallV4SucceedsAndUninstall("v4-digest-v2-Sha512withRSA.apk");
}
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV4WithV2VeritySigner(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// APK generated with:
// --v2-signing-enabled true --v3-signing-enabled false
// --v4-signing-enabled --verity-enabled
// Full commands in generate-apks.sh
assertInstallV4SucceedsAndUninstall("v4-digest-v2-Sha256withDSA-Verity.apk");
assertInstallV4SucceedsAndUninstall("v4-digest-v2-Sha256withEC-Verity.apk");
assertInstallV4SucceedsAndUninstall("v4-digest-v2-Sha256withRSA-Verity.apk");
}
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV4WithV3NoVeritySigner(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// APK generated with:
// --v2-signing-enabled false --v3-signing-enabled true --v4-signing-enabled
// Full commands in generate-apks.sh
assertInstallV4SucceedsAndUninstall("v4-digest-v3-Sha256withDSA.apk");
assertInstallV4SucceedsAndUninstall("v4-digest-v3-Sha256withEC.apk");
assertInstallV4SucceedsAndUninstall("v4-digest-v3-Sha256withRSA.apk");
assertInstallV4SucceedsAndUninstall("v4-digest-v3-Sha512withEC.apk");
assertInstallV4SucceedsAndUninstall("v4-digest-v3-Sha512withRSA.apk");
}
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV4WithV3VeritySigner(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// APK generated with:
// --v2-signing-enabled false --v3-signing-enabled true
// --v4-signing-enabled --verity-enabled
// Full commands in generate-apks.sh
assertInstallV4SucceedsAndUninstall("v4-digest-v3-Sha256withDSA-Verity.apk");
assertInstallV4SucceedsAndUninstall("v4-digest-v3-Sha256withEC-Verity.apk");
assertInstallV4SucceedsAndUninstall("v4-digest-v3-Sha256withRSA-Verity.apk");
}
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV4WithV2SignerDoesNotVerify(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// APKs generated with:
// apksigner sign -v2-signing-enabled true --v3-signing-enabled false --v4-signing-enabled
// Malformed v4 signature - first byte of v4 signing_info.signature is flipped
assertInstallV4FailsWithError("v4-digest-v2-badv4signature.apk", "did not verify");
// Malformed digest - first byte of v4 signing_info.apk_digest is flipped
assertInstallV4FailsWithError("v4-digest-v2-badv2digest.apk", "did not verify");
}
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV4WithV3SignerDoesNotVerify(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// APKs generated with:
// apksigner sign -v2-signing-enabled false --v3-signing-enabled true --v4-signing-enabled
// Malformed v4 signature - first byte of v4 signing_info.signature is flipped
assertInstallV4FailsWithError("v4-digest-v3-badv4signature.apk", "did not verify");
// Malformed digest - first byte of v4 signing_info.apk_digest is flipped
assertInstallV4FailsWithError("v4-digest-v3-badv3digest.apk", "did not verify");
}
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV4WithV2V3SignerDoesNotVerify(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// APKs generated with:
// apksigner sign -v2-signing-enabled true --v3-signing-enabled true --v4-signing-enabled
// Malformed v4 signature - first byte of v4 signing_info.signature is flipped
assertInstallV4FailsWithError("v4-digest-v2v3-badv4signature.apk", "did not verify");
// Malformed digest - first byte of v4 signing_info.apk_digest is flipped
assertInstallV4FailsWithError("v4-digest-v2v3-badv2v3digest.apk", "did not verify");
}
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV4With128BytesAdditionalDataSucceeds(boolean onIncremental)
throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// Editing apksigner to fill additional data of size 128 bytes.
assertInstallV4Succeeds("v4-digest-v3-128bytes-additional-data.apk");
}
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV4With256BytesAdditionalDataFails(boolean onIncremental)
throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// Editing apksigner to fill additional data of size 256 bytes.
if (onIncremental) {
// For incremental, a bad Merkle tree will fail.
assertInstallV4FailsWithError("v4-digest-v3-256bytes-additional-data.apk",
"additionalData has to be at most 128 bytes");
} else {
// For non-incremental, Merkle tree in idsig is not used.
assertInstallV4Succeeds("v4-digest-v3-256bytes-additional-data.apk");
}
}
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV4With10MBytesAdditionalDataFails(boolean onIncremental)
throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// Editing apksigner to fill additional data of size 10 * 1024 * 1024 bytes.
if (onIncremental) {
// For incremental, a bad Merkle tree will fail.
assertInstallV4FailsWithError("v4-digest-v3-10mbytes-additional-data.apk",
"Failure");
} else {
// For non-incremental, Merkle tree in idsig is not used.
assertInstallV4Succeeds("v4-digest-v3-10mbytes-additional-data.apk");
}
}
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV4WithWrongBlockSize(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// Editing apksigner with the wrong block size in the v4 signature.
assertInstallV4FailsWithError("v4-digest-v3-wrong-block-size.apk",
"did not verify");
}
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV4WithDifferentBlockSize(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// Editing apksigner with the different block size (2048 instead of 4096).
assertInstallV4FailsWithError("v4-digest-v3-merkle-tree-different-block-size.apk",
"Unsupported log2BlockSize: 11");
}
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV4WithWrongRawRootHash(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// Editing apksigner with the wrong raw root hash in the v4 signature.
assertInstallV4FailsWithError("v4-digest-v3-wrong-raw-root-hash.apk", "Failure");
}
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV4WithWrongSignatureBytes(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// Editing apksigner with the wrong signature bytes in the v4 signature.
assertInstallV4FailsWithError("v4-digest-v3-wrong-sig-bytes.apk",
"did not verify");
}
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV4WithWrongSignatureBytesSize(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// Editing apksigner with the wrong signature byte size in the v4 signature.
assertInstallV4FailsWithError("v4-digest-v3-wrong-sig-bytes-size.apk",
"Failure");
}
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV4WithNoMerkleTree(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
if (onIncremental) {
// Editing apksigner to not include the Merkle tree.
assertInstallV4FailsWithError("v4-digest-v3-no-merkle-tree.apk",
"Failure");
} else {
assertInstallV4Succeeds("v4-digest-v3-no-merkle-tree.apk");
}
}
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV4WithWithTrailingDataInMerkleTree(boolean onIncremental)
throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// Editing apksigner to add trailing data after the Merkle tree.
if (onIncremental) {
// For incremental, a bad Merkle tree will fail.
assertInstallV4FailsWithError("v4-digest-v3-merkle-tree-1mb-trailing-data.apk",
"Failure");
} else {
// For non-incremental, Merkle tree in idsig is not used.
assertInstallV4Succeeds("v4-digest-v3-merkle-tree-1mb-trailing-data.apk");
}
}
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV4WithMerkleTreeBitsFlipped(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// Editing apksigner to flip few bits in the only node of the Merkle tree of a small app.
if (onIncremental) {
// For incremental, a bad Merkle tree will fail.
assertInstallV4FailsWithError("v4-digest-v3-merkle-tree-bit-flipped.apk",
"Failed to parse");
} else {
// For non-incremental, Merkle tree in idsig is not used.
assertInstallV4Succeeds("v4-digest-v3-merkle-tree-bit-flipped.apk");
}
}
@Test
@Parameters(method = "installOnIncremental")
public void testV4IncToV3NonIncSameKeyUpgradeSucceeds(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// See cts/hostsidetests/appsecurity/res/pkgsigverify/generate-apks.sh for the command
// to generate the apks
assertInstallV4Succeeds("v4-inc-to-v3-noninc-ec-p256-appv1.apk");
// non-incremental upgrade with the same key.
assertInstallSucceeds("v4-inc-to-v3-noninc-ec-p256-appv2.apk");
}
@Test
@Parameters(method = "installOnIncremental")
public void testV4IncToV3NonIncMismatchingKeyUpgradeFails(boolean onIncremental)
throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// See cts/hostsidetests/appsecurity/res/pkgsigverify/generate-apks.sh for the command
// to generate the apks
assertInstallV4Succeeds("v4-inc-to-v3-noninc-ec-p256-appv1.apk");
// non-incremental upgrade with a mismatching key.
assertInstallFailsWithError("v4-inc-to-v3-noninc-ec-p384-appv2.apk",
"signatures do not match newer version");
}
@Test
@Parameters(method = "installOnIncremental")
public void testV4IncToV3NonIncRotatedKeyUpgradeSucceeds(boolean onIncremental)
throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// See cts/hostsidetests/appsecurity/res/pkgsigverify/generate-apks.sh for the command
// to generate the apks
assertInstallV4Succeeds("v4-inc-to-v3-noninc-ec-p256-appv1.apk");
// non-incremental upgrade with key rotation.
assertInstallSucceeds("v4-inc-to-v3-noninc-ec-p384-rotated-ec-p256-appv2.apk");
}
@Test
@Parameters(method = "installOnIncremental")
public void testV4IncToV3NonIncMismatchedRotatedKeyUpgradeFails(boolean onIncremental)
throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// See cts/hostsidetests/appsecurity/res/pkgsigverify/generate-apks.sh for the command
// to generate the apks
assertInstallV4Succeeds("v4-inc-to-v3-noninc-dsa-3072-appv1.apk");
// non-incremental upgrade with key rotation mismatch with key used in app v1.
assertInstallFailsWithError("v4-inc-to-v3-noninc-ec-p384-rotated-ec-p256-appv2.apk",
"signatures do not match newer version");
}
@Test
@Parameters(method = "installOnIncremental")
public void testV4IncToV2NonIncSameKeyUpgradeSucceeds(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// See cts/hostsidetests/appsecurity/res/pkgsigverify/generate-apks.sh for the command
// to generate the apks
assertInstallV4Succeeds("v4-inc-to-v2-noninc-ec-p256-appv1.apk");
// non-incremental upgrade with the same key.
assertInstallSucceeds("v4-inc-to-v2-noninc-ec-p256-appv2.apk");
}
@Test
@Parameters(method = "installOnIncremental")
public void testV4IncToV2NonIncMismatchingKeyUpgradeFails(boolean onIncremental)
throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// See cts/hostsidetests/appsecurity/res/pkgsigverify/generate-apks.sh for the command
// to generate the apks
assertInstallV4Succeeds("v4-inc-to-v2-noninc-ec-p256-appv1.apk");
// non-incremental upgrade with a mismatching key.
assertInstallFailsWithError("v4-inc-to-v2-noninc-ec-p384-appv2.apk",
"signatures do not match newer version");
}
@Test
@Parameters(method = "installOnIncremental")
public void testInstallV4UpdateAfterRotation(boolean onIncremental) throws Exception {
checkAssumptionAndSetIdsigInstallMode(onIncremental);
// This test performs an end to end verification of the update of an app with a rotated
// key. The app under test exports a bound service that performs its own PackageManager key
// rotation API verification, and the instrumentation test binds to the service and invokes
// the verifySignatures method to verify that the key rotation APIs return the expected
// results. The instrumentation test app is signed with the same key and lineage as the
// app under test to also provide a second app that can be used for the checkSignatures
// verification.
// Install the initial versions of the apps; the test method verifies the app under test is
// signed with the original signing key.
assertInstallV4FromBuildSucceeds("CtsSignatureQueryService.apk");
assertInstallV4FromBuildSucceeds("CtsSignatureQueryServiceTest.apk");
Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
"verifySignatures_noRotation_succeeds");
// Install the second version of the app signed with the rotated key. This test verifies the
// app still functions as expected after the update with the rotated key. The
// instrumentation test app is not updated here to allow verification of the pre-key
// rotation behavior for the checkSignatures APIs. These APIs should behave similar to the
// GET_SIGNATURES flag in that if one or both apps have a signing lineage if the oldest
// signers in the lineage match then the methods should return that the signatures match
// even if one is signed with a newer key in the lineage.
assertInstallV4FromBuildSucceeds("CtsSignatureQueryService_v2.apk");
Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
"verifySignatures_withRotation_succeeds");
// Installs the third version of the app under test and the instrumentation test, both
// signed with the same rotated key and lineage. This test is intended to verify that the
// app can still be updated and function as expected after an update with a rotated key.
assertInstallV4FromBuildSucceeds("CtsSignatureQueryService_v3.apk");
assertInstallV4FromBuildSucceeds("CtsSignatureQueryServiceTest_v2.apk");
Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
"verifySignatures_withRotation_succeeds");
}
private boolean hasIncrementalFeature() throws Exception {
return "true\n".equals(getDevice().executeShellCommand(
"pm has-feature android.software.incremental_delivery"));
}
private void checkAssumptionAndSetIdsigInstallMode(boolean onIncremental) throws Exception {
// Due to the limitation of existing test runners (i.e. DeviceParameterizedRunner and
// DeviceJUnit4Parameterized), there's no easy way to parameterize only some of the test
// methods while avoid recording the parameter manually.
mUseIncrementalForInstallWithIdsig = onIncremental;
if (onIncremental) {
// V4 is only enabled on devices with Incremental feature
assumeTrue(hasIncrementalFeature());
} else {
// Install V4 in classic install session
assumeTrue(android.security.Flags.extendVbChainToUpdatedApk());
}
}
private void assertInstallSucceeds(String apkFilenameInResources) throws Exception {
String installResult = installPackageFromResource(apkFilenameInResources);
if (installResult != null) {
fail("Failed to install " + apkFilenameInResources + ": " + installResult);
}
}
private void assertInstallEphemeralSucceeds(String apkFilenameInResources) throws Exception {
String installResult = installEphemeralPackageFromResource(apkFilenameInResources);
if (installResult != null) {
fail("Failed to install " + apkFilenameInResources + ": " + installResult);
}
}
private void assertInstallSucceedsForEach(
String apkFilenamePatternInResources, String[] args) throws Exception {
for (String arg : args) {
String apkFilenameInResources =
String.format(Locale.US, apkFilenamePatternInResources, arg);
String installResult = installPackageFromResource(apkFilenameInResources);
if (installResult != null) {
fail("Failed to install " + apkFilenameInResources + ": " + installResult);
}
try {
uninstallPackage();
} catch (Exception e) {
throw new RuntimeException(
"Failed to uninstall after installing " + apkFilenameInResources, e);
}
}
}
private void assertInstallV4Succeeds(String apkFilenameInResources) throws Exception {
String installResult = installV4PackageFromResource(apkFilenameInResources);
if (!installResult.equals("Success")) {
fail("Failed to install " + apkFilenameInResources + ": " + installResult);
}
}
private void assertInstallV4FromBuildSucceeds(String apkName) throws Exception {
String installResult = installV4PackageFromBuild(apkName);
if (!installResult.equals("Success")) {
fail("Failed to install " + apkName + ": " + installResult);
}
}
private void assertInstallV4SucceedsAndUninstall(String apkFilenameInResources)
throws Exception {
assertInstallV4Succeeds(apkFilenameInResources);
try {
uninstallPackage();
} catch (Exception e) {
throw new RuntimeException(
"Failed to uninstall after installing " + apkFilenameInResources, e);
}
}
private void assertInstallV4FailsWithError(String apkFilenameInResources, String errorSubstring)
throws Exception {
String installResult = installV4PackageFromResource(apkFilenameInResources);
if (installResult.equals("Success")) {
fail("Install of " + apkFilenameInResources + " succeeded but was expected to fail"
+ " with \"" + errorSubstring + "\"");
}
assertContains(
"Install failure message of " + apkFilenameInResources,
errorSubstring,
installResult);
}
private void assertInstallFailsWithError(
String apkFilenameInResources, String errorSubstring) throws Exception {
String installResult = installPackageFromResource(apkFilenameInResources);
if (installResult == null) {
fail("Install of " + apkFilenameInResources + " succeeded but was expected to fail"
+ " with \"" + errorSubstring + "\"");
}
assertContains(
"Install failure message of " + apkFilenameInResources,
errorSubstring,
installResult);
}
private void assertInstallEphemeralFailsWithError(
String apkFilenameInResources, String errorSubstring) throws Exception {
String installResult = installEphemeralPackageFromResource(apkFilenameInResources);
if (installResult == null) {
fail("Install of " + apkFilenameInResources + " succeeded but was expected to fail"
+ " with \"" + errorSubstring + "\"");
}
assertContains(
"Install failure message of " + apkFilenameInResources,
errorSubstring,
installResult);
}
private void assertInstallFails(String apkFilenameInResources) throws Exception {
String installResult = installPackageFromResource(apkFilenameInResources);
if (installResult == null) {
fail("Install of " + apkFilenameInResources + " succeeded but was expected to fail");
}
}
private static void assertContains(String message, String expectedSubstring, String actual) {
String errorPrefix = ((message != null) && (message.length() > 0)) ? (message + ": ") : "";
if (actual == null) {
fail(errorPrefix + "Expected to contain \"" + expectedSubstring + "\", but was null");
}
if (!actual.contains(expectedSubstring)) {
fail(errorPrefix + "Expected to contain \"" + expectedSubstring + "\", but was \""
+ actual + "\"");
}
}
private void installDeviceTestPkg() throws Exception {
assertInstallFromBuildSucceeds(DEVICE_TESTS_APK);
}
private void assertInstallFromBuildSucceeds(String apkName) throws Exception {
String result = installApkFromBuild(apkName);
assertNull("failed to install " + apkName + ", Reason: " + result, result);
}
private void assertInstallFromBuildFails(String apkName) throws Exception {
String result = installApkFromBuild(apkName);
assertNotNull("Successfully installed " + apkName + " when failure was expected", result);
}
private String installApkFromBuild(String apkName) throws Exception {
CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
File apk = buildHelper.getTestFile(apkName);
try {
return getDevice().installPackage(apk, true, INSTALL_ARG_FORCE_QUERYABLE);
} finally {
getDevice().deleteFile("/data/local/tmp/" + apk.getName());
}
}
private String installPackageFromResource(String apkFilenameInResources, boolean ephemeral)
throws IOException, DeviceNotAvailableException {
// ITestDevice.installPackage API requires the APK to be install to be a File. We thus
// copy the requested resource into a temporary file, attempt to install it, and delete the
// file during cleanup.
File apkFile = null;
try {
apkFile = getFileFromResource(apkFilenameInResources);
if (ephemeral) {
return getDevice().installPackage(apkFile, true, "--ephemeral",
INSTALL_ARG_FORCE_QUERYABLE);
} else {
return getDevice().installPackage(apkFile, true, INSTALL_ARG_FORCE_QUERYABLE);
}
} finally {
cleanUpFile(apkFile);
getDevice().deleteFile("/data/local/tmp/" + apkFile.getName());
}
}
private String installV4PackageFromResource(String apkFilenameInResources)
throws IOException, DeviceNotAvailableException {
File apkFile = null;
File v4SignatureFile = null;
String remoteApkFilePath = null, remoteV4SignaturePath = null;
try {
apkFile = getFileFromResource(apkFilenameInResources);
v4SignatureFile = getFileFromResource(apkFilenameInResources + ".idsig");
remoteApkFilePath = pushFileToRemote(apkFile);
remoteV4SignaturePath = pushFileToRemote(v4SignatureFile);
return installV4Package(remoteApkFilePath);
} finally {
cleanUpFile(apkFile);
cleanUpFile(v4SignatureFile);
getDevice().deleteFile(remoteApkFilePath);
getDevice().deleteFile(remoteV4SignaturePath);
}
}
private String installV4PackageFromBuild(String apkName)
throws IOException, DeviceNotAvailableException {
CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
File apkFile = buildHelper.getTestFile(apkName);
File v4SignatureFile = buildHelper.getTestFile(apkName + ".idsig");
String remoteApkFilePath = pushFileToRemote(apkFile);
String remoteV4SignaturePath = pushFileToRemote(v4SignatureFile);
try {
return installV4Package(remoteApkFilePath);
} finally {
getDevice().deleteFile(remoteApkFilePath);
getDevice().deleteFile(remoteV4SignaturePath);
}
}
private String pushFileToRemote(File localFile) throws DeviceNotAvailableException {
String remotePath = "/data/local/tmp/pkginstalltest-" + localFile.getName();
getDevice().pushFile(localFile, remotePath);
return remotePath;
}
private String installV4Package(String remoteApkPath)
throws DeviceNotAvailableException {
var installer = new InstallMultiple();
if (mUseIncrementalForInstallWithIdsig) {
installer.useIncremental();
} else {
// Add to the install session
installer.addRemoteFile(remoteApkPath + ".idsig");
}
return installer
.forceQueryable()
.addRemoteFile(remoteApkPath)
.runForResult();
}
private File getFileFromResource(String filenameInResources)
throws IOException, IllegalArgumentException {
String fullResourceName = TEST_APK_RESOURCE_PREFIX + filenameInResources;
File tempDir = FileUtil.createTempDir("pkginstalltest");
File file = new File(tempDir, filenameInResources);
InputStream in = getClass().getResourceAsStream(fullResourceName);
if (in == null) {
throw new IllegalArgumentException("Resource not found: " + fullResourceName);
}
OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
byte[] buf = new byte[65536];
int chunkSize;
while ((chunkSize = in.read(buf)) != -1) {
out.write(buf, 0, chunkSize);
}
out.close();
return file;
}
private void cleanUpFile(File file) {
if (file != null && file.exists()) {
file.delete();
// Delete the parent dir as well which is a temp dir
File parent = file.getParentFile();
if (parent.exists()) {
parent.delete();
}
}
}
private String installPackageFromResource(String apkFilenameInResources)
throws IOException, DeviceNotAvailableException {
return installPackageFromResource(apkFilenameInResources, false);
}
private String installEphemeralPackageFromResource(String apkFilenameInResources)
throws IOException, DeviceNotAvailableException {
return installPackageFromResource(apkFilenameInResources, true);
}
private String uninstallPackage() throws DeviceNotAvailableException {
String result1 = getDevice().uninstallPackage(TEST_PKG);
String result2 = getDevice().uninstallPackage(TEST_PKG2);
return result1 != null ? result1 : result2;
}
private String uninstallCompanionPackages() throws DeviceNotAvailableException {
String result1 = getDevice().uninstallPackage(COMPANION_TEST_PKG);
String result2 = getDevice().uninstallPackage(COMPANION2_TEST_PKG);
String result3 = getDevice().uninstallPackage(COMPANION3_TEST_PKG);
return Stream.of(result1, result2, result3)
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
}
private String uninstallDeviceTestPackage() throws DeviceNotAvailableException {
return getDevice().uninstallPackage(DEVICE_TESTS_PKG);
}
private void uninstallServicePackages() throws DeviceNotAvailableException {
getDevice().uninstallPackage(SERVICE_PKG);
getDevice().uninstallPackage(SERVICE_TEST_PKG);
}
private void uninstallPackages() throws DeviceNotAvailableException {
uninstallPackage();
uninstallCompanionPackages();
uninstallDeviceTestPackage();
uninstallServicePackages();
}
private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
InstallMultiple() {
super(getDevice(), getBuild(), getAbi(), /* grantPermissions */ true);
}
@Override
protected String deriveRemoteName(String originalName, int index) {
return originalName;
}
}
}