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 INSTALL_ARG_FORCE_QUERYABLE = "--force-queryable";
100 
101     private static final String LOG_TAG = "AppsecurityHostTests";
102 
getTestAppFile(String fileName)103     private File getTestAppFile(String fileName) throws FileNotFoundException {
104         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
105         return buildHelper.getTestFile(fileName);
106     }
107 
108     /**
109      * Helper method that checks that all tests in given result passed, and attempts to generate
110      * a meaningful error message if they failed.
111      *
112      * @param result
113      */
assertDeviceTestsPass(TestRunResult result)114     private void assertDeviceTestsPass(TestRunResult result) {
115         assertFalse(String.format("Failed to successfully run device tests for %s. Reason: %s",
116                 result.getName(), result.getRunFailureMessage()), result.isRunFailure());
117 
118         if (result.hasFailedTests()) {
119 
120             /* build a meaningful error message */
121             StringBuilder errorBuilder = new StringBuilder("on-device tests failed:\n");
122             for (Map.Entry<TestDescription, TestResult> resultEntry :
123                 result.getTestResults().entrySet()) {
124                 if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
125                     errorBuilder.append(resultEntry.getKey().toString());
126                     errorBuilder.append(":\n");
127                     errorBuilder.append(resultEntry.getValue().getStackTrace());
128                 }
129             }
130             fail(errorBuilder.toString());
131         }
132     }
133 
134     /**
135      * Helper method that checks that all tests in given result passed, and attempts to generate
136      * a meaningful error message if they failed.
137      *
138      * @param result
139      */
assertDeviceTestsFail(String msg, TestRunResult result)140     private void assertDeviceTestsFail(String msg, TestRunResult result) {
141         assertFalse(String.format("Failed to successfully run device tests for %s. Reason: %s",
142                 result.getName(), result.getRunFailureMessage()), result.isRunFailure());
143 
144         if (!result.hasFailedTests()) {
145             fail(msg);
146         }
147     }
148 
149     /**
150      * Helper method that will run the specified packages tests on device.
151      *
152      * @param pkgName Android application package for tests
153      * @return <code>true</code> if all tests passed.
154      * @throws DeviceNotAvailableException if connection to device was lost.
155      */
runDeviceTests(String pkgName)156     private boolean runDeviceTests(String pkgName) throws DeviceNotAvailableException {
157         return runDeviceTests(pkgName, null, null);
158     }
159 
160     /**
161      * Helper method that will run the specified packages tests on device.
162      *
163      * @param pkgName Android application package for tests
164      * @return <code>true</code> if all tests passed.
165      * @throws DeviceNotAvailableException if connection to device was lost.
166      */
runDeviceTests(String pkgName, String testClassName, String testMethodName)167     private boolean runDeviceTests(String pkgName, String testClassName, String testMethodName)
168             throws DeviceNotAvailableException {
169         TestRunResult runResult = doRunTests(pkgName, testClassName, testMethodName);
170         return !runResult.hasFailedTests();
171     }
172 
173     /**
174      * Helper method to run tests and return the listener that collected the results.
175      *
176      * @param pkgName Android application package for tests
177      * @return the {@link TestRunResult}
178      * @throws DeviceNotAvailableException if connection to device was lost.
179      */
doRunTests(String pkgName, String testClassName, String testMethodName)180     private TestRunResult doRunTests(String pkgName, String testClassName,
181             String testMethodName) throws DeviceNotAvailableException {
182 
183         RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(pkgName,
184                 RUNNER, getDevice().getIDevice());
185         if (testClassName != null && testMethodName != null) {
186             testRunner.setMethodName(testClassName, testMethodName);
187         }
188         CollectingTestListener listener = new CollectingTestListener();
189         getDevice().runInstrumentationTests(testRunner, listener);
190         return listener.getCurrentRunResults();
191     }
192 
193     /**
194      * Helper method which installs a package and an upgrade to it.
195      *
196      * @param pkgName - package name of apk.
197      * @param firstApk - first apk to install
198      * @param secondApk - apk to which we attempt to upgrade
199      * @param expectedResult - null if successful, otherwise expected error.
200      */
testPackageUpgrade(String pkgName, String firstApk, String secondApk)201     private String testPackageUpgrade(String pkgName, String firstApk,
202              String secondApk) throws Exception {
203         String installResult;
204         try {
205 
206             /* cleanup test apps that might be installed from previous partial test run */
207             mDevice.uninstallPackage(pkgName);
208 
209             installResult = mDevice.installPackage(getTestAppFile(firstApk),
210                     false);
211             /* we should always succeed on first-install */
212             assertNull(String.format("failed to install %s, Reason: %s", pkgName,
213                        installResult), installResult);
214 
215             /* attempt to install upgrade */
216             installResult = mDevice.installPackage(getTestAppFile(secondApk),
217                     true);
218         } finally {
219             mDevice.uninstallPackage(pkgName);
220         }
221         return installResult;
222     }
223     /**
224      * A reference to the device under test.
225      */
226     private ITestDevice mDevice;
227 
228     private IBuildInfo mCtsBuild;
229 
230     /**
231      * {@inheritDoc}
232      */
233     @Override
setBuild(IBuildInfo buildInfo)234     public void setBuild(IBuildInfo buildInfo) {
235         mCtsBuild = buildInfo;
236     }
237 
238     @Override
setUp()239     protected void setUp() throws Exception {
240         super.setUp();
241 
242         Utils.prepareSingleUser(getDevice());
243         assertNotNull(mCtsBuild);
244 
245         mDevice = getDevice();
246     }
247 
248     /**
249      * Tests for KeySet based key rotation
250      */
251 
252     /*
253      * Check if an apk which does not specify an upgrade-key-set may be upgraded
254      * to an apk which does.
255      */
testNoKSToUpgradeKS()256     public void testNoKSToUpgradeKS() throws Exception {
257         String installResult = testPackageUpgrade(KEYSET_PKG, A_SIGNED_NO_UPGRADE, A_SIGNED_A_UPGRADE);
258         assertNull(String.format("failed to upgrade keyset app from no specified upgrade-key-set"
259                 + "to version with specified upgrade-key-set, Reason: %s", installResult),
260                 installResult);
261     }
262 
263     /*
264      * Check if an apk which does specify an upgrade-key-set may be upgraded
265      * to an apk which does not.
266      */
testUpgradeKSToNoKS()267     public void testUpgradeKSToNoKS() throws Exception {
268         String installResult = testPackageUpgrade(KEYSET_PKG, A_SIGNED_A_UPGRADE, A_SIGNED_NO_UPGRADE);
269         assertNull(String.format("failed to upgrade keyset app from specified upgrade-key-set"
270                 + "to version without specified upgrade-key-set, Reason: %s", installResult),
271                 installResult);
272     }
273 
274     /*
275      * Check if an apk signed by a key other than the upgrade keyset can update
276      * an app
277      */
testUpgradeKSWithWrongKey()278     public void testUpgradeKSWithWrongKey() throws Exception {
279         String installResult = testPackageUpgrade(KEYSET_PKG, A_SIGNED_A_UPGRADE, B_SIGNED_A_UPGRADE);
280         assertNotNull("upgrade to improperly signed app succeeded!", installResult);
281     }
282 
283     /*
284      * Check if an apk signed by its signing key, which is not an upgrade key,
285      * can upgrade an app.
286      */
testUpgradeKSWithWrongSigningKey()287     public void testUpgradeKSWithWrongSigningKey() throws Exception {
288         String installResult = testPackageUpgrade(KEYSET_PKG, A_SIGNED_B_UPGRADE, A_SIGNED_B_UPGRADE);
289          assertNotNull("upgrade to improperly signed app succeeded!",
290                  installResult);
291     }
292 
293     /*
294      * Check if an apk signed by its upgrade key, which is not its signing key,
295      * can upgrade an app.
296      */
testUpgradeKSWithUpgradeKey()297     public void testUpgradeKSWithUpgradeKey() throws Exception {
298         String installResult = testPackageUpgrade(KEYSET_PKG, A_SIGNED_B_UPGRADE, B_SIGNED_B_UPGRADE);
299         assertNull(String.format("failed to upgrade keyset app from one signed by key-a"
300                  + "to version signed by upgrade-key-set key-b, Reason: %s", installResult),
301                  installResult);
302     }
303 
304     /*
305      * Check if an apk signed by its upgrade key, which is its signing key, can
306      * upgrade an app.
307      */
testUpgradeKSWithSigningUpgradeKey()308     public void testUpgradeKSWithSigningUpgradeKey() throws Exception {
309         String installResult = testPackageUpgrade(KEYSET_PKG, A_SIGNED_A_UPGRADE, A_SIGNED_A_UPGRADE);
310         assertNull(String.format("failed to upgrade keyset app from one signed by key-a"
311                     + "to version signed by upgrade-key-set key-b, Reason: %s", installResult),
312                     installResult);
313     }
314 
315     /*
316      * Check if an apk signed by multiple keys, one of which is its upgrade key,
317      * can upgrade an app.
318      */
testMultipleUpgradeKSWithUpgradeKey()319     public void testMultipleUpgradeKSWithUpgradeKey() throws Exception {
320         String installResult = testPackageUpgrade(KEYSET_PKG, A_SIGNED_A_UPGRADE,
321                 A_AND_B_SIGNED_A_UPGRADE);
322         assertNull(String.format("failed to upgrade keyset app from one signed by key-a"
323                 + "to version signed by upgrade-key-set key-b, Reason: %s", installResult),
324                 installResult);
325     }
326 
327     /*
328      * Check if an apk signed by multiple keys, its signing keys,
329      * but none of which is an upgrade key, can upgrade an app.
330      */
testMultipleUpgradeKSWithSigningKey()331     public void testMultipleUpgradeKSWithSigningKey() throws Exception {
332         String installResult = testPackageUpgrade(KEYSET_PKG, A_AND_C_SIGNED_B_UPGRADE,
333                 A_AND_C_SIGNED_B_UPGRADE);
334         assertNotNull("upgrade to improperly signed app succeeded!", installResult);
335     }
336 
337     /*
338      * Check if an apk which defines multiple (two) upgrade keysets is
339      * upgrade-able by either.
340      */
testUpgradeKSWithMultipleUpgradeKeySetsFirstKey()341     public void testUpgradeKSWithMultipleUpgradeKeySetsFirstKey() throws Exception {
342         String installResult = testPackageUpgrade(KEYSET_PKG, A_SIGNED_A_OR_B_UPGRADE,
343                 A_SIGNED_A_UPGRADE);
344         assertNull(String.format("failed to upgrade keyset app from one signed by key-a"
345                 + "to one signed by first upgrade keyset key-a, Reason: %s", installResult),
346                 installResult);
347         installResult = testPackageUpgrade(KEYSET_PKG, A_SIGNED_A_OR_B_UPGRADE,
348                 B_SIGNED_B_UPGRADE);
349         assertNull(String.format("failed to upgrade keyset app from one signed by key-a"
350                 + "to one signed by second upgrade keyset key-b, Reason: %s", installResult),
351                 installResult);
352     }
353 
354     /**
355      * Helper method which installs a package defining a permission and a package
356      * using the permission, and then rotates the signing keys for one of them.
357      * A device-side test is then used to ascertain whether or not the permission
358      * was appropriately gained or lost.
359      *
360      * @param permDefApk - apk to install which defines the sig-permissoin
361      * @param permUseApk - apk to install which declares it uses the permission
362      * @param upgradeApk - apk to install which upgrades one of the first two
363      * @param hasPermBeforeUpgrade - whether we expect the consuming app to have
364      *        the permission before the upgrade takes place.
365      * @param hasPermAfterUpgrade - whether we expect the consuming app to have
366      *        the permission after the upgrade takes place.
367      */
testKeyRotationPerm(String permDefApk, String permUseApk, String upgradeApk, boolean hasPermBeforeUpgrade, boolean hasPermAfterUpgrade)368     private void testKeyRotationPerm(String permDefApk, String permUseApk,
369             String upgradeApk, boolean hasPermBeforeUpgrade,
370             boolean hasPermAfterUpgrade) throws Exception {
371         try {
372 
373             /* cleanup test apps that might be installed from previous partial test run */
374             mDevice.uninstallPackage(KEYSET_PKG);
375             mDevice.uninstallPackage(KEYSET_PERM_DEF_PKG);
376             mDevice.uninstallPackage(KEYSET_TEST_PKG);
377 
378             /* install PERM_DEF, KEYSET_APP and KEYSET_TEST_APP */
379             String installResult = mDevice.installPackage(
380                     getTestAppFile(permDefApk), false, INSTALL_ARG_FORCE_QUERYABLE);
381             assertNull(String.format("failed to install keyset perm-def app, Reason: %s",
382                        installResult), installResult);
383             installResult = getDevice().installPackage(
384                     getTestAppFile(permUseApk), false, INSTALL_ARG_FORCE_QUERYABLE);
385             assertNull(String.format("failed to install keyset test app. Reason: %s",
386                     installResult), installResult);
387             installResult = getDevice().installPackage(
388                     getTestAppFile(KEYSET_TEST_APP_APK), false);
389             assertNull(String.format("failed to install keyset test app. Reason: %s",
390                     installResult), installResult);
391 
392             /* verify package does have perm */
393             TestRunResult result = doRunTests(KEYSET_TEST_PKG, PERM_TEST_CLASS,
394                     "testHasPerm");
395             if (hasPermBeforeUpgrade) {
396                 assertDeviceTestsPass(result);
397             } else {
398                 assertDeviceTestsFail(" has permission permission it should not have.", result);
399             }
400 
401             /* rotate keys */
402             installResult = mDevice.installPackage(getTestAppFile(upgradeApk),
403                     true, INSTALL_ARG_FORCE_QUERYABLE);
404             result = doRunTests(KEYSET_TEST_PKG, PERM_TEST_CLASS,
405                     "testHasPerm");
406             if (hasPermAfterUpgrade) {
407                 assertDeviceTestsPass(result);
408             } else {
409                 assertDeviceTestsFail(KEYSET_PKG + " has permission it should not have.", result);
410             }
411         } finally {
412             mDevice.uninstallPackage(KEYSET_PKG);
413             mDevice.uninstallPackage(KEYSET_PERM_DEF_PKG);
414             mDevice.uninstallPackage(KEYSET_TEST_PKG);
415         }
416     }
417 
418     /*
419      * Check if an apk gains signature-level permission after changing to a new
420      * signature, for which a permission should be granted.
421      */
testUpgradeSigPermGained()422     public void testUpgradeSigPermGained() throws Exception {
423         testKeyRotationPerm(PERM_DEF_A_SIGNED, PERM_USE_B_SIGNED, PERM_USE_A_SIGNED,
424                 false, true);
425     }
426 
427     /*
428      * Check if an apk loses signature-level permission after changing to a new
429      * signature, from one for which a permission was previously granted.
430      */
testUpgradeSigPermLost()431     public void testUpgradeSigPermLost() throws Exception {
432         testKeyRotationPerm(PERM_DEF_A_SIGNED, PERM_USE_A_SIGNED, PERM_USE_B_SIGNED,
433                 true, false);
434     }
435 
436     /*
437      * Check if an apk gains signature-level permission after the app defining
438      * it rotates to the same signature.
439      */
testUpgradeDefinerSigPermGained()440     public void testUpgradeDefinerSigPermGained() throws Exception {
441         testKeyRotationPerm(PERM_DEF_A_SIGNED, PERM_USE_B_SIGNED, PERM_DEF_B_SIGNED,
442                 false, true);
443     }
444 
445     /*
446      * Check if an apk loses signature-level permission after the app defining
447      * it rotates to a different signature.
448      */
testUpgradeDefinerSigPermLost()449     public void testUpgradeDefinerSigPermLost() throws Exception {
450         testKeyRotationPerm(PERM_DEF_A_SIGNED, PERM_USE_A_SIGNED, PERM_DEF_B_SIGNED,
451                 true, false);
452     }
453 
454     /*
455      * Check if an apk which indicates it uses a sharedUserId and defines an
456      * upgrade keyset is allowed to rotate to that keyset.
457      */
testUpgradeSharedUser()458     public void testUpgradeSharedUser() throws Exception {
459         String installResult = testPackageUpgrade(KEYSET_PKG, SHARED_USR_A_SIGNED_B_UPGRADE,
460                 SHARED_USR_B_SIGNED_B_UPGRADE);
461         assertNotNull("upgrade allowed for app with shareduserid!", installResult);
462     }
463 
464     /*
465      * Check that an apk with an upgrade key represented by a bad public key
466      * fails to install.
467      */
testBadUpgradeBadPubKey()468     public void testBadUpgradeBadPubKey() throws Exception {
469         mDevice.uninstallPackage(KEYSET_PKG);
470         String installResult = mDevice.installPackage(getTestAppFile(A_SIGNED_BAD_B_B_UPGRADE),
471                 false);
472         assertNotNull("Installation of apk with upgrade key referring to a bad public key succeeded!",
473                 installResult);
474     }
475 
476     /*
477      * Check that an apk with an upgrade keyset that includes a bad public key fails to install.
478      */
testBadUpgradeMissingPubKey()479     public void testBadUpgradeMissingPubKey() throws Exception {
480         mDevice.uninstallPackage(KEYSET_PKG);
481         String installResult = mDevice.installPackage(getTestAppFile(C_SIGNED_BAD_A_AB_UPGRADE),
482                 false);
483         assertNotNull("Installation of apk with upgrade key referring to a bad public key succeeded!",
484                 installResult);
485     }
486 
487     /*
488      * Check that an apk with an upgrade key that has no corresponding public key fails to install.
489      */
testBadUpgradeNoPubKey()490     public void testBadUpgradeNoPubKey() throws Exception {
491         mDevice.uninstallPackage(KEYSET_PKG);
492         String installResult = mDevice.installPackage(getTestAppFile(A_SIGNED_NO_B_B_UPGRADE),
493                 false);
494         assertNotNull("Installation of apk with upgrade key referring to a bad public key succeeded!",
495                 installResult);
496     }
497 
498     /*
499      * Check if an apk signed by RSA pub key can upgrade to apk signed by EC key.
500      */
testUpgradeKSRsaToEC()501     public void testUpgradeKSRsaToEC() throws Exception {
502         String installResult = testPackageUpgrade(KEYSET_PKG, A_SIGNED_EC_A_UPGRADE,
503                 EC_A_SIGNED_A_UPGRADE);
504         assertNull(String.format("failed to upgrade keyset app from one signed by RSA key"
505                  + "to version signed by EC upgrade-key-set, Reason: %s", installResult),
506                  installResult);
507     }
508 
509     /*
510      * Check if an apk signed by EC pub key can upgrade to apk signed by RSA key.
511      */
testUpgradeKSECToRSA()512     public void testUpgradeKSECToRSA() throws Exception {
513         String installResult = testPackageUpgrade(KEYSET_PKG, EC_A_SIGNED_A_UPGRADE,
514                 A_SIGNED_EC_A_UPGRADE);
515         assertNull(String.format("failed to upgrade keyset app from one signed by EC key"
516                  + "to version signed by RSA upgrade-key-set, Reason: %s", installResult),
517                  installResult);
518     }
519 }
520