blob: 04e769d7dcfb438e4a8ef8c408830a259547c7c9 [file] [log] [blame]
/*
* Copyright (C) 2018 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 com.android.server.pm;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.content.Context;
import android.content.pm.PackageParser;
import android.content.pm.Signature;
import android.util.Xml;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.HexDump;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.xmlpull.v1.XmlPullParser;
import java.io.File;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@RunWith(AndroidJUnit4.class)
public class PackageSignaturesTest {
private static final String TEST_RESOURCES_FOLDER = "PackageSignaturesTest";
private Context mContext;
private PackageSetting mPackageSetting;
// These signatures are the DER encoding of the ec-p256[_X] X509 certificates in the certs/
// directory. The apksigner tool was used to sign a test APK with these certificates and the
// corresponding ec-p256{_X].pk8 private key file. For the lineage tests the
// ec-p256-lineage-X-signers file was provided as the parameter to the --lineage option when
// signing the APK. The APK was then installed on a test device, the packages.xml file was
// pulled from the device, and the APK's <sig> tag was used as the basis for these tests.
// For more details see the README under the xml/ directory.
private static final String FIRST_EXPECTED_SIGNATURE =
"3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a8648ce3d04030230123110300e06"
+ "035504030c0765632d70323536301e170d3136303333313134353830365a170d34333038313731343538"
+ "30365a30123110300e06035504030c0765632d703235363059301306072a8648ce3d020106082a8648ce"
+ "3d03010703420004a65f113d22cb4913908307ac31ee2ba0e9138b785fac6536d14ea2ce90d2b4bfe194"
+ "b50cdc8e169f54a73a991ef0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e04"
+ "160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d23041830168014d4133568b95b"
+ "30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d0403020349"
+ "003046022100f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16db78d6022100f8"
+ "eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0fdbb8042cb655aadd";
private static final String SECOND_EXPECTED_SIGNATURE =
"3082016d30820113a0030201020209008855bd1dd2b2b225300a06082a8648ce3d04030230123110300e06"
+ "035504030c0765632d70323536301e170d3138303731333137343135315a170d32383037313031373431"
+ "35315a30143112301006035504030c0965632d703235365f323059301306072a8648ce3d020106082a86"
+ "48ce3d030107034200041d4cca0472ad97ee3cecef0da93d62b450c6788333b36e7553cde9f74ab5df00"
+ "bbba6ba950e68461d70bbc271b62151dad2de2bf6203cd2076801c7a9d4422e1a350304e301d0603551d"
+ "0e041604147991d92b0208fc448bf506d4efc9fff428cb5e5f301f0603551d23041830168014d4133568"
+ "b95b30158b322071ea8c43ff5b05ccc8300c0603551d13040530030101ff300a06082a8648ce3d040302"
+ "034800304502202769abb1b49fc2f53479c4ae92a6631dabfd522c9acb0bba2b43ebeb99c63011022100"
+ "d260fb1d1f176cf9b7fa60098bfd24319f4905a3e5fda100a6fe1a2ab19ff09e";
private static final String THIRD_EXPECTED_SIGNATURE =
"3082016e30820115a0030201020209008394f5cad16a89a7300a06082a8648ce3d04030230143112301006"
+ "035504030c0965632d703235365f32301e170d3138303731343030303532365a170d3238303731313030"
+ "303532365a30143112301006035504030c0965632d703235365f333059301306072a8648ce3d02010608"
+ "2a8648ce3d03010703420004f31e62430e9db6fc5928d975fc4e47419bacfcb2e07c89299e6cd7e344dd"
+ "21adfd308d58cb49a1a2a3fecacceea4862069f30be1643bcc255040d8089dfb3743a350304e301d0603"
+ "551d0e041604146f8d0828b13efaf577fc86b0e99fa3e54bcbcff0301f0603551d230418301680147991"
+ "d92b0208fc448bf506d4efc9fff428cb5e5f300c0603551d13040530030101ff300a06082a8648ce3d04"
+ "030203470030440220256bdaa2784c273e4cc291a595a46779dee9de9044dc9f7ab820309567df9fe902"
+ "201a4ad8c69891b5a8c47434fe9540ed1f4979b5fad3483f3fa04d5677355a579e";
// When running tests using the pastSigs tag / lineage the past signers and their capabilities
// should be returned in the SigningDetails. The flags attribute of the cert tag under the
// pastSigs tag contains these capabilities; for tests that verify the lineage the capabilities
// of the signers should be set to the values in this Map.
private static final Map<String, Integer> SIGNATURE_TO_CAPABILITY_MAP;
static {
SIGNATURE_TO_CAPABILITY_MAP = new HashMap<>();
SIGNATURE_TO_CAPABILITY_MAP.put(FIRST_EXPECTED_SIGNATURE, 3);
SIGNATURE_TO_CAPABILITY_MAP.put(SECOND_EXPECTED_SIGNATURE, 7);
SIGNATURE_TO_CAPABILITY_MAP.put(THIRD_EXPECTED_SIGNATURE, 23);
}
private static final int[] CAPABILITIES =
{PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA,
PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID,
PackageParser.SigningDetails.CertCapabilities.PERMISSION,
PackageParser.SigningDetails.CertCapabilities.ROLLBACK};
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getContext();
mPackageSetting = createPackageSetting();
}
@Test
public void testReadXmlWithOneSignerCompletesSuccessfully() throws Exception {
// Verifies the good path of reading a single sigs tag with one signer returns the
// expected signature and scheme version.
verifyReadXmlReturnsExpectedSignatures("xml/one-signer.xml", 1, FIRST_EXPECTED_SIGNATURE);
}
@Test
public void testReadXmlWithTwoV1V2Signers() throws Exception {
// Verifies the good path of reading a single sigs tag with multiple signers returns the
// expected signatures and scheme version.
verifyReadXmlReturnsExpectedSignatures("xml/two-signers-v1v2.xml", 2,
FIRST_EXPECTED_SIGNATURE, SECOND_EXPECTED_SIGNATURE);
}
@Test
public void testReadXmlFromTwoSigsTagsWithSameSigner() throws Exception {
// Verifies the good path of reading two separate packages tags from the same signer. The
// first call to readXml should return the list with the expected signature, then the second
// call should reference this signature and complete successfully with no new entries in the
// List.
XmlPullParser parser = getXMLFromResources("xml/one-signer.xml");
ArrayList<Signature> signatures = new ArrayList<>();
mPackageSetting.signatures.readXml(parser, signatures);
Set<String> expectedSignatures = createSetOfSignatures(FIRST_EXPECTED_SIGNATURE);
verifySignaturesContainExpectedValues(signatures, expectedSignatures);
parser = getXMLFromResources("xml/one-signer-previous-cert.xml");
mPackageSetting.signatures.readXml(parser, signatures);
expectedSignatures = createSetOfSignatures(FIRST_EXPECTED_SIGNATURE);
verifySignaturesContainExpectedValues(signatures, expectedSignatures);
}
@Test
public void testReadXmlWithSigningLineage() throws Exception {
// Verifies the good path of reading a single sigs tag including pastSigs with the
// signing lineage returns the expected signatures and lineage for two and three signers
// in the lineage.
verifyReadXmlReturnsExpectedSignaturesAndLineage("xml/two-signers-in-lineage.xml", 3,
FIRST_EXPECTED_SIGNATURE, SECOND_EXPECTED_SIGNATURE);
verifyReadXmlReturnsExpectedSignaturesAndLineage("xml/three-signers-in-lineage.xml", 3,
FIRST_EXPECTED_SIGNATURE, SECOND_EXPECTED_SIGNATURE, THIRD_EXPECTED_SIGNATURE);
}
@Test
public void testReadXmlWithInvalidPublicKeyInCertKey() throws Exception {
// If the cert tag key attribute does not contain a valid public key then a
// CertificateException should be thrown when attempting to build the SigningDetails; in
// this case the signing details should be set to UNKNOWN.
XmlPullParser parser = getXMLFromResources(
"xml/one-signer-invalid-public-key-cert-key.xml");
ArrayList<Signature> signatures = new ArrayList<>();
mPackageSetting.signatures.readXml(parser, signatures);
assertEquals(
"The signing details was not UNKNOWN after parsing an invalid public key cert key"
+ " attribute",
PackageParser.SigningDetails.UNKNOWN, mPackageSetting.signatures.mSigningDetails);
}
@Test
public void testReadXmlWithMissingSigsCount() throws Exception {
// Verifies if the sigs count attribute is missing then the signature cannot be read but the
// method does not throw an exception.
verifyReadXmlReturnsExpectedSignatures("xml/one-signer-missing-sigs-count.xml",
PackageParser.SigningDetails.SignatureSchemeVersion.UNKNOWN);
}
@Test
public void testReadXmlWithMissingSchemeVersion() throws Exception {
// Verifies if the schemeVersion is an invalid value the signature can still be obtained.
verifyReadXmlReturnsExpectedSignatures("xml/one-signer-missing-scheme-version.xml",
PackageParser.SigningDetails.SignatureSchemeVersion.UNKNOWN,
FIRST_EXPECTED_SIGNATURE);
}
@Test
public void testReadXmlWithSigningLineageWithMissingSchemeVersion() throws Exception {
// Verifies if the scheme version cannot be read the signers in the lineage can still be
// obtained.
verifyReadXmlReturnsExpectedSignaturesAndLineage(
"xml/three-signers-in-lineage-missing-scheme-version.xml",
PackageParser.SigningDetails.SignatureSchemeVersion.UNKNOWN,
FIRST_EXPECTED_SIGNATURE, SECOND_EXPECTED_SIGNATURE, THIRD_EXPECTED_SIGNATURE);
}
@Test
public void testReadXmlWithInvalidCertIndex() throws Exception {
// If the cert index attribute is invalid the signature will not be read but the call
// should exit gracefully.
verifyReadXmlReturnsExpectedSignatures("xml/one-signer-invalid-cert-index.xml", 3);
}
@Test
public void testReadXmlWithMissingCertIndex() throws Exception {
// If the cert index attribute is missing the signature will not be read but the call should
// exit gracefully.
verifyReadXmlReturnsExpectedSignatures("xml/one-signer-missing-cert-index.xml", 3);
}
@Test
public void testReadXmlWithInvalidCertKey() throws Exception {
// If the cert key value is invalid the signature cannot be read but the call should exit
// gracefully.
verifyReadXmlReturnsExpectedSignatures("xml/one-signer-invalid-cert-key.xml", 3);
}
@Test
public void testReadXmlWithMissingCertKey() throws Exception {
// If the cert key is missing the signature cannot be read but the call should exit
// gracefully.
verifyReadXmlReturnsExpectedSignatures("xml/one-signer-missing-cert-key.xml", 3);
}
@Test
public void testReadXmlWithMissingCertTag() throws Exception {
// If the cert tag is missing there is no signature to read but the call should exit
// gracefully.
verifyReadXmlReturnsExpectedSignatures("xml/one-signer-missing-cert-tag.xml", 3);
}
@Test
public void testReadXmlWithTooFewCertTags() throws Exception {
// If the number of cert tags is less than that specified in the count attribute then the
// signatures that could be read are copied to a smaller array to be used when building
// the SigningDetails object. This test verifies if there are too few cert tags the
// available signatures can still be obtained.
verifyReadXmlReturnsExpectedSignatures("xml/two-signers-v1v2-missing-cert-tag.xml", 1,
FIRST_EXPECTED_SIGNATURE);
}
@Test
public void testReadXmlWithExtraCertTag() throws Exception {
// Verifies if there are more cert tags than specified by the count attribute the extra cert
// tag is ignored and the expected signature from the first cert tag is returned.
verifyReadXmlReturnsExpectedSignatures("xml/one-signer-extra-cert-tag.xml", 3,
FIRST_EXPECTED_SIGNATURE);
}
@Test
public void testReadXmlWithInvalidTag() throws Exception {
// Verifies an invalid tag under sigs is ignored and the expected signature is returned.
verifyReadXmlReturnsExpectedSignatures("xml/one-signer-invalid-tag.xml", 3,
FIRST_EXPECTED_SIGNATURE);
}
@Test
public void testReadXmlWithInvalidPastSigsCount() throws Exception {
// Verifies if the pastSigs tag contains an invalid count attribute the current signature
// is still returned; in this case the third expected signature is the most recent signer.
verifyReadXmlReturnsExpectedSignatures(
"xml/three-signers-in-lineage-invalid-pastSigs-count.xml", 3,
THIRD_EXPECTED_SIGNATURE);
}
@Test
public void testReadXmlWithMissingPastSigsCount() throws Exception {
// Verifies if the pastSigs tag is missing the count attribute the current signature is
// still returned; in this case the third expected signature is the most recent signer.
verifyReadXmlReturnsExpectedSignaturesAndLineage(
"xml/three-signers-in-lineage-missing-pastSigs-count.xml", 3,
THIRD_EXPECTED_SIGNATURE);
}
@Test
public void testReadXmlWithInvalidCertFlags() throws Exception {
// Verifies if the cert tag contains an invalid flags attribute the expected signatures
// are still returned, although since the flags could not be read these signatures will not
// include the capabilities of the previous signers in the lineage.
verifyReadXmlReturnsExpectedSignatures("xml/two-signers-in-lineage-invalid-certs-flags.xml",
3, FIRST_EXPECTED_SIGNATURE, SECOND_EXPECTED_SIGNATURE);
}
@Test
public void testReadXmlWithMissingCertFlags() throws Exception {
// Verifies if the cert tag does not contain a flags attribute the expected signatures are
// still returned, although since there are no flags to read these signatures will not
// include the capabilities of the previous signers in the lineage.
verifyReadXmlReturnsExpectedSignatures("xml/two-signers-in-lineage-missing-certs-flags.xml",
3, FIRST_EXPECTED_SIGNATURE, SECOND_EXPECTED_SIGNATURE);
}
@Test
public void testReadXmlWithMultiplePastSigsTags() throws Exception {
// Verifies if multiple pastSigs tags are found under the sigs tag the additional pastSigs
// tag is ignored and the expected signatures are returned along with the previous signer in
// the lineage.
verifyReadXmlReturnsExpectedSignaturesAndLineage(
"xml/two-signers-in-lineage-multiple-pastSigs-tags.xml", 3,
FIRST_EXPECTED_SIGNATURE, SECOND_EXPECTED_SIGNATURE);
}
@Test
public void testReadXmlWithInvalidPastSigsCertIndex() throws Exception {
// If the pastSigs cert tag contains an invalid index attribute that signature cannot be
// read but the current signature should still be returned.
verifyReadXmlReturnsExpectedSignaturesAndLineage(
"xml/two-signers-in-lineage-invalid-pastSigs-cert-index.xml", 3,
SECOND_EXPECTED_SIGNATURE);
}
@Test
public void testReadXmlWithMissingPastSigsCertIndex() throws Exception {
// If the pastSigs cert tag does not contain an index attribute that signature cannot be
// read but the current signature should still be returned.
verifyReadXmlReturnsExpectedSignaturesAndLineage(
"xml/two-signers-in-lineage-missing-pastSigs-cert-index.xml", 3,
SECOND_EXPECTED_SIGNATURE);
}
@Test
public void testReadXmlWithUndefinedPastSigsIndex() throws Exception {
// If a cert tag does not contain a key attribute it is assumed that the index attribute
// refers to a previously seen signature. If a signature does not yet exist at this index
// then the current signature cannot be read but any other signatures should still be
// returned.
verifyReadXmlReturnsExpectedSignatures(
"xml/two-signers-in-lineage-undefined-pastSigs-index.xml", 3,
FIRST_EXPECTED_SIGNATURE, null);
}
@Test
public void testReadXmlWithTooFewPastSigsCertTags() throws Exception {
// If the number of cert tags is less than that specified in the count attribute of the
// pastSigs tag then the signatures that could be read are copied to a smaller array to be
// used when building the SigningDetails object. This test verifies if there are too few
// cert tags the available signatures and lineage can still be obtained.
verifyReadXmlReturnsExpectedSignaturesAndLineage(
"xml/three-signers-in-lineage-missing-pastSigs-cert-tag.xml", 3,
FIRST_EXPECTED_SIGNATURE, THIRD_EXPECTED_SIGNATURE);
}
@Test
public void testReadXmlWithPastSignerWithNoCapabilities() throws Exception {
// When rotating the signing key a developer is able to specify the capabilities granted to
// the apps signed with the previous key. This test verifies a previous signing certificate
// with the flags set to 0 does not have any capabilities.
XmlPullParser parser = getXMLFromResources("xml/two-signers-in-lineage-no-caps.xml");
ArrayList<Signature> signatures = new ArrayList<>();
mPackageSetting.signatures.readXml(parser, signatures);
// obtain the Signature in the list matching the previous signing certificate
Signature previousSignature = null;
for (Signature signature : signatures) {
String signatureValue = HexDump.toHexString(signature.toByteArray(), false);
if (signatureValue.equals(FIRST_EXPECTED_SIGNATURE)) {
previousSignature = signature;
break;
}
}
assertNotNull("Unable to find the expected previous signer", previousSignature);
for (int capability : CAPABILITIES) {
assertFalse("The previous signer should not have the " + capability + " capability",
mPackageSetting.signatures.mSigningDetails.hasCertificate(previousSignature,
capability));
}
}
/**
* Verifies reading the sigs tag of the provided XML file returns the specified signature scheme
* version and the provided signatures.
*/
private void verifyReadXmlReturnsExpectedSignatures(String xmlFile, int expectedSchemeVersion,
String... expectedSignatureValues) throws Exception {
XmlPullParser parser = getXMLFromResources(xmlFile);
ArrayList<Signature> signatures = new ArrayList<>();
mPackageSetting.signatures.readXml(parser, signatures);
Set<String> expectedSignatures = createSetOfSignatures(expectedSignatureValues);
verifySignaturesContainExpectedValues(signatures, expectedSignatures);
assertEquals("The returned signature scheme is not the expected value",
expectedSchemeVersion,
mPackageSetting.signatures.mSigningDetails.signatureSchemeVersion);
}
/**
* Verifies reading the sigs tag of the provided XML file returns the specified signature scheme
* version, the provided signatures, and that the previous signers have the expected
* capabilities.
*/
private void verifyReadXmlReturnsExpectedSignaturesAndLineage(String xmlFile,
int schemeVersion, String... expectedSignatureValues) throws Exception {
XmlPullParser parser = getXMLFromResources(xmlFile);
ArrayList<Signature> signatures = new ArrayList<>();
mPackageSetting.signatures.readXml(parser, signatures);
Set<String> expectedSignatures = createSetOfSignatures(expectedSignatureValues);
verifySignaturesContainExpectedValues(signatures, expectedSignatures);
assertEquals("The returned signature scheme is not the expected value", schemeVersion,
mPackageSetting.signatures.mSigningDetails.signatureSchemeVersion);
for (Signature signature : signatures) {
String signatureValue = HexDump.toHexString(signature.toByteArray(), false);
int expectedCapabilities = SIGNATURE_TO_CAPABILITY_MAP.get(signatureValue);
assertTrue("The signature " + signatureValue
+ " was not found with the expected capabilities of " +
expectedCapabilities
+ " in the signing details",
mPackageSetting.signatures.mSigningDetails.hasCertificate(signature,
expectedCapabilities));
}
}
/**
* Verifies the provided {@code List} contains Signatures that match the provided hex encoded
* signature values.
*
* The provided {@code Set} will be modified by this method as elements will be removed to
* ensure duplicate expected Signatures are not in the {@code List}.
*/
private static void verifySignaturesContainExpectedValues(ArrayList<Signature> signatures,
Set<String> expectedSignatures) {
assertEquals("The number of signatures does not equal the expected number of signatures",
expectedSignatures.size(), signatures.size());
for (Signature signature : signatures) {
String signatureString = null;
if (signature != null) {
signatureString = HexDump.toHexString(signature.toByteArray(), false);
}
// If the signature is in the expected set then remove it so that duplicate matching
// signatures are reported.
if (expectedSignatures.contains(signatureString)) {
expectedSignatures.remove(signatureString);
} else {
fail("The following unexpected signature was returned: " + signatureString);
}
}
}
private static Set<String> createSetOfSignatures(String... signatures) {
Set<String> result = new HashSet<String>();
for (String signature : signatures) {
result.add(signature);
}
return result;
}
private XmlPullParser getXMLFromResources(String xmlFile) throws Exception {
InputStream xmlStream = mContext.getResources().getAssets().open(
TEST_RESOURCES_FOLDER + "/" + xmlFile);
XmlPullParser result = Xml.newPullParser();
result.setInput(xmlStream, StandardCharsets.UTF_8.name());
int type;
// advance the parser to the first tag
while ((type = result.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
;
}
return result;
}
private static PackageSetting createPackageSetting() {
// Generic PackageSetting object with values from a test app installed on a device to be
// used to test the methods under the PackageSignatures signatures data member.
File appPath = new File("/data/app/app");
PackageSetting result = new PackageSetting("test.app", null, appPath, appPath,
"/data/app/app", null, null, null,
1, 940097092, 0, 0 /*userId*/, null, null);
return result;
}
}