• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
getTestAppFile(String fileName)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      */
assertDeviceTestsPass(TestRunResult result)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      */
assertDeviceTestsFail(String msg, TestRunResult result)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      */
runDeviceTests(String pkgName)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      */
runDeviceTests(String pkgName, String testClassName, String testMethodName)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      */
doRunTests(String pkgName, String testClassName, String testMethodName)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      */
testPackageUpgrade(String pkgName, String firstApk, String secondApk)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
setBuild(IBuildInfo buildInfo)232     public void setBuild(IBuildInfo buildInfo) {
233         mCtsBuild = buildInfo;
234     }
235 
236     @Override
setUp()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      */
testNoKSToUpgradeKS()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      */
testUpgradeKSToNoKS()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      */
testUpgradeKSWithWrongKey()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      */
testUpgradeKSWithWrongSigningKey()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      */
testUpgradeKSWithUpgradeKey()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      */
testUpgradeKSWithSigningUpgradeKey()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      */
testMultipleUpgradeKSWithUpgradeKey()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      */
testMultipleUpgradeKSWithSigningKey()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      */
testUpgradeKSWithMultipleUpgradeKeySetsFirstKey()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      */
testKeyRotationPerm(String permDefApk, String permUseApk, String upgradeApk, boolean hasPermBeforeUpgrade, boolean hasPermAfterUpgrade)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      */
testUpgradeSigPermGained()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      */
testUpgradeSigPermLost()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      */
testUpgradeDefinerSigPermGained()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      */
testUpgradeDefinerSigPermLost()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      */
testUpgradeSharedUser()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      */
testBadUpgradeBadPubKey()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      */
testBadUpgradeMissingPubKey()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      */
testBadUpgradeNoPubKey()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      */
testUpgradeKSRsaToEC()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      */
testUpgradeKSECToRSA()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