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