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