blob: 4374efe2725b71fb00cdecca409868c334c7c323 [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 android.platform.test.annotations.AsbSecurityTest;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.testtype.DeviceTestCase;
import com.android.tradefed.testtype.IBuildReceiver;
import com.android.tradefed.util.FileUtil;
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;
/**
* Tests for APK signature verification during installation.
*/
public class PkgInstallSignatureVerificationTest extends DeviceTestCase implements IBuildReceiver {
private static final String TEST_PKG = "android.appsecurity.cts.tinyapp";
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 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 IBuildInfo mCtsBuild;
@Override
public void setBuild(IBuildInfo buildInfo) {
mCtsBuild = buildInfo;
}
@Override
protected void setUp() throws Exception {
super.setUp();
Utils.prepareSingleUser(getDevice());
assertNotNull(mCtsBuild);
uninstallPackage();
uninstallCompanionPackages();
installDeviceTestPkg();
}
@Override
protected void tearDown() throws Exception {
try {
uninstallPackages();
} catch (DeviceNotAvailableException ignored) {
} finally {
super.tearDown();
}
}
public void testInstallOriginalSucceeds() throws Exception {
// APK signed with v1 and v2 schemes. Obtained by building
// cts/hostsidetests/appsecurity/test-apps/tinyapp.
assertInstallSucceeds("original.apk");
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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.
// 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);
// }
//
// 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);
// }
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");
}
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
);
}
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);
}
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);
}
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);
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
public void testInstallMaxSizedZipEocdComment() throws Exception {
// Obtained by modifying apksigner to produce a max-sized (0xffff bytes long) ZIP End of
// Central Directory comment, and signing the original.apk using the modified apksigner.
assertInstallSucceeds("v1-only-max-sized-eocd-comment.apk");
assertInstallSucceeds("v2-only-max-sized-eocd-comment.apk");
}
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
}
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)
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);
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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-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");
}
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 the
// installation of this updated app 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");
assertInstallFromBuildFails("v3-ec-p256-with-por_1_2-no-shUid-cap-sharedUid.apk");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
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");
}
public void testInstallV4WithV2Signer() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// APK generated with:
// apksigner sign --v2-signing-enabled true --v3-signing-enabled false --v4-signing-enabled
assertInstallV4Succeeds("v4-digest-v2.apk");
}
public void testInstallV4WithV3Signer() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// APK generated with:
// apksigner sign --v2-signing-enabled false --v3-signing-enabled true --v4-signing-enabled
assertInstallV4Succeeds("v4-digest-v3.apk");
}
public void testInstallV4WithV2V3Signer() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// APK generated with:
// apksigner sign --v2-signing-enabled true --v3-signing-enabled true --v4-signing-enabled
assertInstallV4Succeeds("v4-digest-v2v3.apk");
}
public void testInstallV4WithV2NoVeritySigner() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// 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");
}
public void testInstallV4WithV2VeritySigner() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// 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");
}
public void testInstallV4WithV3NoVeritySigner() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// 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");
}
public void testInstallV4WithV3VeritySigner() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// 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");
}
public void testInstallV4WithV2SignerDoesNotVerify() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// 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");
}
public void testInstallV4WithV3SignerDoesNotVerify() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// 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");
}
public void testInstallV4WithV2V3SignerDoesNotVerify() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// 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");
}
public void testInstallV4With128BytesAdditionalDataSucceeds() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// Editing apksigner to fill additional data of size 128 bytes.
assertInstallV4Succeeds("v4-digest-v3-128bytes-additional-data.apk");
}
public void testInstallV4With256BytesAdditionalDataFails() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// Editing apksigner to fill additional data of size 256 bytes.
assertInstallV4FailsWithError("v4-digest-v3-256bytes-additional-data.apk",
"additionalData has to be at most 128 bytes");
}
public void testInstallV4With10MBytesAdditionalDataFails() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// Editing apksigner to fill additional data of size 10 * 1024 * 1024 bytes.
assertInstallV4FailsWithError("v4-digest-v3-10mbytes-additional-data.apk",
"Failure");
}
public void testInstallV4WithWrongBlockSize() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// Editing apksigner with the wrong block size in the v4 signature.
assertInstallV4FailsWithError("v4-digest-v3-wrong-block-size.apk",
"did not verify");
}
public void testInstallV4WithDifferentBlockSize() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// Editing apksigner with the different block size (2048 instead of 4096).
assertInstallV4FailsWithError("v4-digest-v3-merkle-tree-different-block-size.apk",
"Unsupported log2BlockSize: 11");
}
public void testInstallV4WithWrongRawRootHash() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// Editing apksigner with the wrong raw root hash in the v4 signature.
assertInstallV4FailsWithError("v4-digest-v3-wrong-raw-root-hash.apk", "Failure");
}
public void testInstallV4WithWrongSignatureBytes() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// Editing apksigner with the wrong signature bytes in the v4 signature.
assertInstallV4FailsWithError("v4-digest-v3-wrong-sig-bytes.apk",
"did not verify");
}
public void testInstallV4WithWrongSignatureBytesSize() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// Editing apksigner with the wrong signature byte size in the v4 signature.
assertInstallV4FailsWithError("v4-digest-v3-wrong-sig-bytes-size.apk",
"Failure");
}
public void testInstallV4WithNoMerkleTree() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// Editing apksigner to not include the Merkle tree.
assertInstallV4FailsWithError("v4-digest-v3-no-merkle-tree.apk",
"Failure");
}
public void testInstallV4WithWithTrailingDataInMerkleTree() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// Editing apksigner to add trailing data after the Merkle tree
assertInstallV4FailsWithError("v4-digest-v3-merkle-tree-1mb-trailing-data.apk",
"Failure");
}
public void testInstallV4WithMerkleTreeBitsFlipped() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// Editing apksigner to flip few bits in the only node of the Merkle tree of a small app.
assertInstallV4FailsWithError("v4-digest-v3-merkle-tree-bit-flipped.apk",
"Failed to parse");
}
public void testV4IncToV3NonIncSameKeyUpgradeSucceeds() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// 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");
}
public void testV4IncToV3NonIncMismatchingKeyUpgradeFails() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// 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 previously installed version");
}
public void testV4IncToV3NonIncRotatedKeyUpgradeSucceeds() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// 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");
}
public void testV4IncToV3NonIncMismatchedRotatedKeyUpgradeFails() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// 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 previously installed version");
}
public void testV4IncToV2NonIncSameKeyUpgradeSucceeds() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// 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");
}
public void testV4IncToV2NonIncMismatchingKeyUpgradeFails() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// 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 previously installed version");
}
public void testInstallV4UpdateAfterRotation() throws Exception {
// V4 is only enabled on devices with Incremental feature
if (!hasIncrementalFeature()) {
return;
}
// 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 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\n")) {
fail("Failed to install " + apkFilenameInResources + ": " + installResult);
}
}
private void assertInstallV4FromBuildSucceeds(String apkName) throws Exception {
String installResult = installV4PackageFromBuild(apkName);
if (!installResult.equals("Success\n")) {
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\n")) {
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(mCtsBuild);
File apk = buildHelper.getTestFile(apkName);
return getDevice().installPackage(apk, true, INSTALL_ARG_FORCE_QUERYABLE);
}
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);
}
}
private String installV4PackageFromResource(String apkFilenameInResources)
throws IOException, DeviceNotAvailableException {
File apkFile = null;
File v4SignatureFile = null;
try {
apkFile = getFileFromResource(apkFilenameInResources);
v4SignatureFile = getFileFromResource(apkFilenameInResources + ".idsig");
String remoteApkFilePath = pushFileToRemote(apkFile);
pushFileToRemote(v4SignatureFile);
return installV4Package(remoteApkFilePath);
} finally {
cleanUpFile(apkFile);
cleanUpFile(v4SignatureFile);
}
}
private String installV4PackageFromBuild(String apkName)
throws IOException, DeviceNotAvailableException {
CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
File apkFile = buildHelper.getTestFile(apkName);
File v4SignatureFile = buildHelper.getTestFile(apkName + ".idsig");
String remoteApkFilePath = pushFileToRemote(apkFile);
pushFileToRemote(v4SignatureFile);
return installV4Package(remoteApkFilePath);
}
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 {
String command = "pm install-incremental --force-queryable -t -g " + remoteApkPath;
return getDevice().executeShellCommand(command);
}
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();
}
}
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 {
return getDevice().uninstallPackage(TEST_PKG);
}
private String uninstallCompanionPackages() throws DeviceNotAvailableException {
String result1 = getDevice().uninstallPackage(COMPANION_TEST_PKG);
String result2 = getDevice().uninstallPackage(COMPANION2_TEST_PKG);
return result1 != null ? result1 : result2;
}
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();
}
}