Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.appsecurity.cts;
     18 
     19 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
     20 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
     21 import com.android.ddmlib.testrunner.TestResult.TestStatus;
     22 import com.android.tradefed.build.IBuildInfo;
     23 import com.android.tradefed.device.DeviceNotAvailableException;
     24 import com.android.tradefed.device.ITestDevice;
     25 import com.android.tradefed.result.CollectingTestListener;
     26 import com.android.tradefed.result.TestDescription;
     27 import com.android.tradefed.result.TestResult;
     28 import com.android.tradefed.result.TestRunResult;
     29 import com.android.tradefed.testtype.DeviceTestCase;
     30 import com.android.tradefed.testtype.IBuildReceiver;
     31 
     32 import java.io.File;
     33 import java.io.FileNotFoundException;
     34 import java.util.Map;
     35 
     36 /**
     37  * Tests for Keyset based features.
     38  */
     39 public class KeySetHostTest extends DeviceTestCase implements IBuildReceiver {
     40 
     41     private static final String RUNNER = "androidx.test.runner.AndroidJUnitRunner";
     42 
     43     /* package with device-side tests */
     44     private static final String KEYSET_TEST_PKG = "com.android.cts.keysets.testapp";
     45     private static final String KEYSET_TEST_APP_APK = "CtsKeySetTestApp.apk";
     46 
     47     /* plain test apks with different signing and upgrade keysets */
     48     private static final String KEYSET_PKG = "com.android.cts.keysets";
     49     private static final String A_SIGNED_NO_UPGRADE =
     50             "CtsKeySetSigningAUpgradeNone.apk";
     51     private static final String A_SIGNED_A_UPGRADE =
     52             "CtsKeySetSigningAUpgradeA.apk";
     53     private static final String A_SIGNED_B_UPGRADE =
     54             "CtsKeySetSigningAUpgradeB.apk";
     55     private static final String A_SIGNED_A_OR_B_UPGRADE =
     56             "CtsKeySetSigningAUpgradeAOrB.apk";
     57     private static final String B_SIGNED_A_UPGRADE =
     58             "CtsKeySetSigningBUpgradeA.apk";
     59     private static final String B_SIGNED_B_UPGRADE =
     60             "CtsKeySetSigningBUpgradeB.apk";
     61     private static final String A_AND_B_SIGNED_A_UPGRADE =
     62             "CtsKeySetSigningAAndBUpgradeA.apk";
     63     private static final String A_AND_B_SIGNED_B_UPGRADE =
     64             "CtsKeySetSigningAAndBUpgradeB.apk";
     65     private static final String A_AND_C_SIGNED_B_UPGRADE =
     66             "CtsKeySetSigningAAndCUpgradeB.apk";
     67     private static final String SHARED_USR_A_SIGNED_B_UPGRADE =
     68             "CtsKeySetSharedUserSigningAUpgradeB.apk";
     69     private static final String SHARED_USR_B_SIGNED_B_UPGRADE =
     70             "CtsKeySetSharedUserSigningBUpgradeB.apk";
     71     private static final String A_SIGNED_BAD_B_B_UPGRADE =
     72             "CtsKeySetSigningABadUpgradeB.apk";
     73     private static final String C_SIGNED_BAD_A_AB_UPGRADE =
     74             "CtsKeySetSigningCBadAUpgradeAB.apk";
     75     private static final String A_SIGNED_NO_B_B_UPGRADE =
     76             "CtsKeySetSigningANoDefUpgradeB.apk";
     77     private static final String A_SIGNED_EC_A_UPGRADE =
     78             "CtsKeySetSigningAUpgradeEcA.apk";
     79     private static final String EC_A_SIGNED_A_UPGRADE =
     80             "CtsKeySetSigningEcAUpgradeA.apk";
     81 
     82     /* package which defines the KEYSET_PERM_NAME signature permission */
     83     private static final String KEYSET_PERM_DEF_PKG =
     84             "com.android.cts.keysets_permdef";
     85 
     86     /* The apks defining and using the permission have both A and B as upgrade keys */
     87     private static final String PERM_DEF_A_SIGNED =
     88             "CtsKeySetPermDefSigningA.apk";
     89     private static final String PERM_DEF_B_SIGNED =
     90             "CtsKeySetPermDefSigningB.apk";
     91     private static final String PERM_USE_A_SIGNED =
     92             "CtsKeySetPermUseSigningA.apk";
     93     private static final String PERM_USE_B_SIGNED =
     94             "CtsKeySetPermUseSigningB.apk";
     95 
     96     private static final String PERM_TEST_CLASS =
     97         "com.android.cts.keysets.KeySetPermissionsTest";
     98 
     99     private static final String LOG_TAG = "AppsecurityHostTests";
    100 
    101     private File getTestAppFile(String fileName) throws FileNotFoundException {
    102         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
    103         return buildHelper.getTestFile(fileName);
    104     }
    105 
    106     /**
    107      * Helper method that checks that all tests in given result passed, and attempts to generate
    108      * a meaningful error message if they failed.
    109      *
    110      * @param result
    111      */
    112     private void assertDeviceTestsPass(TestRunResult result) {
    113         assertFalse(String.format("Failed to successfully run device tests for %s. Reason: %s",
    114                 result.getName(), result.getRunFailureMessage()), result.isRunFailure());
    115 
    116         if (result.hasFailedTests()) {
    117 
    118             /* build a meaningful error message */
    119             StringBuilder errorBuilder = new StringBuilder("on-device tests failed:\n");
    120             for (Map.Entry<TestDescription, TestResult> resultEntry :
    121                 result.getTestResults().entrySet()) {
    122                 if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
    123                     errorBuilder.append(resultEntry.getKey().toString());
    124                     errorBuilder.append(":\n");
    125                     errorBuilder.append(resultEntry.getValue().getStackTrace());
    126                 }
    127             }
    128             fail(errorBuilder.toString());
    129         }
    130     }
    131 
    132     /**
    133      * Helper method that checks that all tests in given result passed, and attempts to generate
    134      * a meaningful error message if they failed.
    135      *
    136      * @param result
    137      */
    138     private void assertDeviceTestsFail(String msg, TestRunResult result) {
    139         assertFalse(String.format("Failed to successfully run device tests for %s. Reason: %s",
    140                 result.getName(), result.getRunFailureMessage()), result.isRunFailure());
    141 
    142         if (!result.hasFailedTests()) {
    143             fail(msg);
    144         }
    145     }
    146 
    147     /**
    148      * Helper method that will run the specified packages tests on device.
    149      *
    150      * @param pkgName Android application package for tests
    151      * @return <code>true</code> if all tests passed.
    152      * @throws DeviceNotAvailableException if connection to device was lost.
    153      */
    154     private boolean runDeviceTests(String pkgName) throws DeviceNotAvailableException {
    155         return runDeviceTests(pkgName, null, null);
    156     }
    157 
    158     /**
    159      * Helper method that will run the specified packages tests on device.
    160      *
    161      * @param pkgName Android application package for tests
    162      * @return <code>true</code> if all tests passed.
    163      * @throws DeviceNotAvailableException if connection to device was lost.
    164      */
    165     private boolean runDeviceTests(String pkgName, String testClassName, String testMethodName)
    166             throws DeviceNotAvailableException {
    167         TestRunResult runResult = doRunTests(pkgName, testClassName, testMethodName);
    168         return !runResult.hasFailedTests();
    169     }
    170 
    171     /**
    172      * Helper method to run tests and return the listener that collected the results.
    173      *
    174      * @param pkgName Android application package for tests
    175      * @return the {@link TestRunResult}
    176      * @throws DeviceNotAvailableException if connection to device was lost.
    177      */
    178     private TestRunResult doRunTests(String pkgName, String testClassName,
    179             String testMethodName) throws DeviceNotAvailableException {
    180 
    181         RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(pkgName,
    182                 RUNNER, getDevice().getIDevice());
    183         if (testClassName != null && testMethodName != null) {
    184             testRunner.setMethodName(testClassName, testMethodName);
    185         }
    186         CollectingTestListener listener = new CollectingTestListener();
    187         getDevice().runInstrumentationTests(testRunner, listener);
    188         return listener.getCurrentRunResults();
    189     }
    190 
    191     /**
    192      * Helper method which installs a package and an upgrade to it.
    193      *
    194      * @param pkgName - package name of apk.
    195      * @param firstApk - first apk to install
    196      * @param secondApk - apk to which we attempt to upgrade
    197      * @param expectedResult - null if successful, otherwise expected error.
    198      */
    199     private String testPackageUpgrade(String pkgName, String firstApk,
    200              String secondApk) throws Exception {
    201         String installResult;
    202         try {
    203 
    204             /* cleanup test apps that might be installed from previous partial test run */
    205             mDevice.uninstallPackage(pkgName);
    206 
    207             installResult = mDevice.installPackage(getTestAppFile(firstApk),
    208                     false);
    209             /* we should always succeed on first-install */
    210             assertNull(String.format("failed to install %s, Reason: %s", pkgName,
    211                        installResult), installResult);
    212 
    213             /* attempt to install upgrade */
    214             installResult = mDevice.installPackage(getTestAppFile(secondApk),
    215                     true);
    216         } finally {
    217             mDevice.uninstallPackage(pkgName);
    218         }
    219         return installResult;
    220     }
    221     /**
    222      * A reference to the device under test.
    223      */
    224     private ITestDevice mDevice;
    225 
    226     private IBuildInfo mCtsBuild;
    227 
    228     /**
    229      * {@inheritDoc}
    230      */
    231     @Override
    232     public void setBuild(IBuildInfo buildInfo) {
    233         mCtsBuild = buildInfo;
    234     }
    235 
    236     @Override
    237     protected void setUp() throws Exception {
    238         super.setUp();
    239 
    240         Utils.prepareSingleUser(getDevice());
    241         assertNotNull(mCtsBuild);
    242 
    243         mDevice = getDevice();
    244     }
    245 
    246     /**
    247      * Tests for KeySet based key rotation
    248      */
    249 
    250     /*
    251      * Check if an apk which does not specify an upgrade-key-set may be upgraded
    252      * to an apk which does.
    253      */
    254     public void testNoKSToUpgradeKS() throws Exception {
    255         String installResult = testPackageUpgrade(KEYSET_PKG, A_SIGNED_NO_UPGRADE, A_SIGNED_A_UPGRADE);
    256         assertNull(String.format("failed to upgrade keyset app from no specified upgrade-key-set"
    257                 + "to version with specified upgrade-key-set, Reason: %s", installResult),
    258                 installResult);
    259     }
    260 
    261     /*
    262      * Check if an apk which does specify an upgrade-key-set may be upgraded
    263      * to an apk which does not.
    264      */
    265     public void testUpgradeKSToNoKS() throws Exception {
    266         String installResult = testPackageUpgrade(KEYSET_PKG, A_SIGNED_A_UPGRADE, A_SIGNED_NO_UPGRADE);
    267         assertNull(String.format("failed to upgrade keyset app from specified upgrade-key-set"
    268                 + "to version without specified upgrade-key-set, Reason: %s", installResult),
    269                 installResult);
    270     }
    271 
    272     /*
    273      * Check if an apk signed by a key other than the upgrade keyset can update
    274      * an app
    275      */
    276     public void testUpgradeKSWithWrongKey() throws Exception {
    277         String installResult = testPackageUpgrade(KEYSET_PKG, A_SIGNED_A_UPGRADE, B_SIGNED_A_UPGRADE);
    278         assertNotNull("upgrade to improperly signed app succeeded!", installResult);
    279     }
    280 
    281     /*
    282      * Check if an apk signed by its signing key, which is not an upgrade key,
    283      * can upgrade an app.
    284      */
    285     public void testUpgradeKSWithWrongSigningKey() throws Exception {
    286         String installResult = testPackageUpgrade(KEYSET_PKG, A_SIGNED_B_UPGRADE, A_SIGNED_B_UPGRADE);
    287          assertNotNull("upgrade to improperly signed app succeeded!",
    288                  installResult);
    289     }
    290 
    291     /*
    292      * Check if an apk signed by its upgrade key, which is not its signing key,
    293      * can upgrade an app.
    294      */
    295     public void testUpgradeKSWithUpgradeKey() throws Exception {
    296         String installResult = testPackageUpgrade(KEYSET_PKG, A_SIGNED_B_UPGRADE, B_SIGNED_B_UPGRADE);
    297         assertNull(String.format("failed to upgrade keyset app from one signed by key-a"
    298                  + "to version signed by upgrade-key-set key-b, Reason: %s", installResult),
    299                  installResult);
    300     }
    301 
    302     /*
    303      * Check if an apk signed by its upgrade key, which is its signing key, can
    304      * upgrade an app.
    305      */
    306     public void testUpgradeKSWithSigningUpgradeKey() throws Exception {
    307         String installResult = testPackageUpgrade(KEYSET_PKG, A_SIGNED_A_UPGRADE, A_SIGNED_A_UPGRADE);
    308         assertNull(String.format("failed to upgrade keyset app from one signed by key-a"
    309                     + "to version signed by upgrade-key-set key-b, Reason: %s", installResult),
    310                     installResult);
    311     }
    312 
    313     /*
    314      * Check if an apk signed by multiple keys, one of which is its upgrade key,
    315      * can upgrade an app.
    316      */
    317     public void testMultipleUpgradeKSWithUpgradeKey() throws Exception {
    318         String installResult = testPackageUpgrade(KEYSET_PKG, A_SIGNED_A_UPGRADE,
    319                 A_AND_B_SIGNED_A_UPGRADE);
    320         assertNull(String.format("failed to upgrade keyset app from one signed by key-a"
    321                 + "to version signed by upgrade-key-set key-b, Reason: %s", installResult),
    322                 installResult);
    323     }
    324 
    325     /*
    326      * Check if an apk signed by multiple keys, its signing keys,
    327      * but none of which is an upgrade key, can upgrade an app.
    328      */
    329     public void testMultipleUpgradeKSWithSigningKey() throws Exception {
    330         String installResult = testPackageUpgrade(KEYSET_PKG, A_AND_C_SIGNED_B_UPGRADE,
    331                 A_AND_C_SIGNED_B_UPGRADE);
    332         assertNotNull("upgrade to improperly signed app succeeded!", installResult);
    333     }
    334 
    335     /*
    336      * Check if an apk which defines multiple (two) upgrade keysets is
    337      * upgrade-able by either.
    338      */
    339     public void testUpgradeKSWithMultipleUpgradeKeySetsFirstKey() throws Exception {
    340         String installResult = testPackageUpgrade(KEYSET_PKG, A_SIGNED_A_OR_B_UPGRADE,
    341                 A_SIGNED_A_UPGRADE);
    342         assertNull(String.format("failed to upgrade keyset app from one signed by key-a"
    343                 + "to one signed by first upgrade keyset key-a, Reason: %s", installResult),
    344                 installResult);
    345         installResult = testPackageUpgrade(KEYSET_PKG, A_SIGNED_A_OR_B_UPGRADE,
    346                 B_SIGNED_B_UPGRADE);
    347         assertNull(String.format("failed to upgrade keyset app from one signed by key-a"
    348                 + "to one signed by second upgrade keyset key-b, Reason: %s", installResult),
    349                 installResult);
    350     }
    351 
    352     /**
    353      * Helper method which installs a package defining a permission and a package
    354      * using the permission, and then rotates the signing keys for one of them.
    355      * A device-side test is then used to ascertain whether or not the permission
    356      * was appropriately gained or lost.
    357      *
    358      * @param permDefApk - apk to install which defines the sig-permissoin
    359      * @param permUseApk - apk to install which declares it uses the permission
    360      * @param upgradeApk - apk to install which upgrades one of the first two
    361      * @param hasPermBeforeUpgrade - whether we expect the consuming app to have
    362      *        the permission before the upgrade takes place.
    363      * @param hasPermAfterUpgrade - whether we expect the consuming app to have
    364      *        the permission after the upgrade takes place.
    365      */
    366     private void testKeyRotationPerm(String permDefApk, String permUseApk,
    367             String upgradeApk, boolean hasPermBeforeUpgrade,
    368             boolean hasPermAfterUpgrade) throws Exception {
    369         try {
    370 
    371             /* cleanup test apps that might be installed from previous partial test run */
    372             mDevice.uninstallPackage(KEYSET_PKG);
    373             mDevice.uninstallPackage(KEYSET_PERM_DEF_PKG);
    374             mDevice.uninstallPackage(KEYSET_TEST_PKG);
    375 
    376             /* install PERM_DEF, KEYSET_APP and KEYSET_TEST_APP */
    377             String installResult = mDevice.installPackage(
    378                     getTestAppFile(permDefApk), false);
    379             assertNull(String.format("failed to install keyset perm-def app, Reason: %s",
    380                        installResult), installResult);
    381             installResult = getDevice().installPackage(
    382                     getTestAppFile(permUseApk), false);
    383             assertNull(String.format("failed to install keyset test app. Reason: %s",
    384                     installResult), installResult);
    385             installResult = getDevice().installPackage(
    386                     getTestAppFile(KEYSET_TEST_APP_APK), false);
    387             assertNull(String.format("failed to install keyset test app. Reason: %s",
    388                     installResult), installResult);
    389 
    390             /* verify package does have perm */
    391             TestRunResult result = doRunTests(KEYSET_TEST_PKG, PERM_TEST_CLASS,
    392                     "testHasPerm");
    393             if (hasPermBeforeUpgrade) {
    394                 assertDeviceTestsPass(result);
    395             } else {
    396                 assertDeviceTestsFail(" has permission permission it should not have.", result);
    397             }
    398 
    399             /* rotate keys */
    400             installResult = mDevice.installPackage(getTestAppFile(upgradeApk),
    401                     true);
    402             result = doRunTests(KEYSET_TEST_PKG, PERM_TEST_CLASS,
    403                     "testHasPerm");
    404             if (hasPermAfterUpgrade) {
    405                 assertDeviceTestsPass(result);
    406             } else {
    407                 assertDeviceTestsFail(KEYSET_PKG + " has permission it should not have.", result);
    408             }
    409         } finally {
    410             mDevice.uninstallPackage(KEYSET_PKG);
    411             mDevice.uninstallPackage(KEYSET_PERM_DEF_PKG);
    412             mDevice.uninstallPackage(KEYSET_TEST_PKG);
    413         }
    414     }
    415 
    416     /*
    417      * Check if an apk gains signature-level permission after changing to a new
    418      * signature, for which a permission should be granted.
    419      */
    420     public void testUpgradeSigPermGained() throws Exception {
    421         testKeyRotationPerm(PERM_DEF_A_SIGNED, PERM_USE_B_SIGNED, PERM_USE_A_SIGNED,
    422                 false, true);
    423     }
    424 
    425     /*
    426      * Check if an apk loses signature-level permission after changing to a new
    427      * signature, from one for which a permission was previously granted.
    428      */
    429     public void testUpgradeSigPermLost() throws Exception {
    430         testKeyRotationPerm(PERM_DEF_A_SIGNED, PERM_USE_A_SIGNED, PERM_USE_B_SIGNED,
    431                 true, false);
    432     }
    433 
    434     /*
    435      * Check if an apk gains signature-level permission after the app defining
    436      * it rotates to the same signature.
    437      */
    438     public void testUpgradeDefinerSigPermGained() throws Exception {
    439         testKeyRotationPerm(PERM_DEF_A_SIGNED, PERM_USE_B_SIGNED, PERM_DEF_B_SIGNED,
    440                 false, true);
    441     }
    442 
    443     /*
    444      * Check if an apk loses signature-level permission after the app defining
    445      * it rotates to a different signature.
    446      */
    447     public void testUpgradeDefinerSigPermLost() throws Exception {
    448         testKeyRotationPerm(PERM_DEF_A_SIGNED, PERM_USE_A_SIGNED, PERM_DEF_B_SIGNED,
    449                 true, false);
    450     }
    451 
    452     /*
    453      * Check if an apk which indicates it uses a sharedUserId and defines an
    454      * upgrade keyset is allowed to rotate to that keyset.
    455      */
    456     public void testUpgradeSharedUser() throws Exception {
    457         String installResult = testPackageUpgrade(KEYSET_PKG, SHARED_USR_A_SIGNED_B_UPGRADE,
    458                 SHARED_USR_B_SIGNED_B_UPGRADE);
    459         assertNotNull("upgrade allowed for app with shareduserid!", installResult);
    460     }
    461 
    462     /*
    463      * Check that an apk with an upgrade key represented by a bad public key
    464      * fails to install.
    465      */
    466     public void testBadUpgradeBadPubKey() throws Exception {
    467         mDevice.uninstallPackage(KEYSET_PKG);
    468         String installResult = mDevice.installPackage(getTestAppFile(A_SIGNED_BAD_B_B_UPGRADE),
    469                 false);
    470         assertNotNull("Installation of apk with upgrade key referring to a bad public key succeeded!",
    471                 installResult);
    472     }
    473 
    474     /*
    475      * Check that an apk with an upgrade keyset that includes a bad public key fails to install.
    476      */
    477     public void testBadUpgradeMissingPubKey() throws Exception {
    478         mDevice.uninstallPackage(KEYSET_PKG);
    479         String installResult = mDevice.installPackage(getTestAppFile(C_SIGNED_BAD_A_AB_UPGRADE),
    480                 false);
    481         assertNotNull("Installation of apk with upgrade key referring to a bad public key succeeded!",
    482                 installResult);
    483     }
    484 
    485     /*
    486      * Check that an apk with an upgrade key that has no corresponding public key fails to install.
    487      */
    488     public void testBadUpgradeNoPubKey() throws Exception {
    489         mDevice.uninstallPackage(KEYSET_PKG);
    490         String installResult = mDevice.installPackage(getTestAppFile(A_SIGNED_NO_B_B_UPGRADE),
    491                 false);
    492         assertNotNull("Installation of apk with upgrade key referring to a bad public key succeeded!",
    493                 installResult);
    494     }
    495 
    496     /*
    497      * Check if an apk signed by RSA pub key can upgrade to apk signed by EC key.
    498      */
    499     public void testUpgradeKSRsaToEC() throws Exception {
    500         String installResult = testPackageUpgrade(KEYSET_PKG, A_SIGNED_EC_A_UPGRADE,
    501                 EC_A_SIGNED_A_UPGRADE);
    502         assertNull(String.format("failed to upgrade keyset app from one signed by RSA key"
    503                  + "to version signed by EC upgrade-key-set, Reason: %s", installResult),
    504                  installResult);
    505     }
    506 
    507     /*
    508      * Check if an apk signed by EC pub key can upgrade to apk signed by RSA key.
    509      */
    510     public void testUpgradeKSECToRSA() throws Exception {
    511         String installResult = testPackageUpgrade(KEYSET_PKG, EC_A_SIGNED_A_UPGRADE,
    512                 A_SIGNED_EC_A_UPGRADE);
    513         assertNull(String.format("failed to upgrade keyset app from one signed by EC key"
    514                  + "to version signed by RSA upgrade-key-set, Reason: %s", installResult),
    515                  installResult);
    516     }
    517 }
    518