1 /* 2 * Copyright (C) 2016 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 com.android.cts.encryptionapp; 18 19 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; 20 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; 21 22 import static com.google.common.truth.Truth.assertThat; 23 import static com.google.common.truth.Truth.assertWithMessage; 24 25 import android.accessibilityservice.AccessibilityService; 26 import android.app.KeyguardManager; 27 import android.content.BroadcastReceiver; 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.IntentFilter; 32 import android.content.pm.ComponentInfo; 33 import android.content.pm.PackageManager; 34 import android.content.pm.PackageManager.NameNotFoundException; 35 import android.database.Cursor; 36 import android.net.Uri; 37 import android.os.Environment; 38 import android.os.PowerManager; 39 import android.os.StrictMode; 40 import android.os.StrictMode.ViolationInfo; 41 import android.os.SystemClock; 42 import android.os.UserManager; 43 import android.os.strictmode.CredentialProtectedWhileLockedViolation; 44 import android.os.strictmode.ImplicitDirectBootViolation; 45 import android.os.strictmode.Violation; 46 import android.provider.Settings; 47 import android.test.InstrumentationTestCase; 48 import android.util.Log; 49 import android.view.KeyEvent; 50 51 import androidx.test.uiautomator.UiDevice; 52 53 import com.android.compatibility.common.util.TestUtils; 54 55 import java.io.File; 56 import java.util.Arrays; 57 import java.util.concurrent.CountDownLatch; 58 import java.util.concurrent.LinkedBlockingQueue; 59 import java.util.concurrent.TimeUnit; 60 import java.util.function.BooleanSupplier; 61 import java.util.function.Consumer; 62 63 public class EncryptionAppTest extends InstrumentationTestCase { 64 private static final String TAG = "EncryptionAppTest"; 65 66 private static final String KEY_BOOT = "boot"; 67 68 private static final String TEST_PKG = "com.android.cts.encryptionapp"; 69 private static final String TEST_ACTION = "com.android.cts.encryptionapp.TEST"; 70 71 private static final String OTHER_PKG = "com.android.cts.splitapp"; 72 73 private static final int BOOT_TIMEOUT_SECONDS = 150; 74 private static final int UNLOCK_SCREEN_START_TIME_SECONDS = 10; 75 76 private static final Uri FILE_INFO_URI = Uri.parse("content://" + OTHER_PKG + "/files"); 77 78 private Context mCe; 79 private Context mDe; 80 private PackageManager mPm; 81 82 private UiDevice mDevice; 83 private AwareActivity mActivity; 84 85 @Override setUp()86 public void setUp() throws Exception { 87 super.setUp(); 88 89 mCe = getInstrumentation().getContext(); 90 mDe = mCe.createDeviceProtectedStorageContext(); 91 mPm = mCe.getPackageManager(); 92 93 mDevice = UiDevice.getInstance(getInstrumentation()); 94 assertNotNull(mDevice); 95 } 96 97 @Override tearDown()98 public void tearDown() throws Exception { 99 super.tearDown(); 100 101 if (mActivity != null) { 102 mActivity.finish(); 103 } 104 } 105 testSetUp()106 public void testSetUp() throws Exception { 107 // Write both CE/DE data for ourselves 108 assertTrue("CE file", getTestFile(mCe).createNewFile()); 109 assertTrue("DE file", getTestFile(mDe).createNewFile()); 110 111 doBootCountBefore(); 112 113 mActivity = launchActivity(getInstrumentation().getTargetContext().getPackageName(), 114 AwareActivity.class, null); 115 mDevice.waitForIdle(); 116 117 // Set a PIN for this user 118 mDevice.executeShellCommand("locksettings set-disabled false"); 119 String output = mDevice.executeShellCommand("locksettings set-pin 1234"); 120 assertTrue("set-pin failed. Output: " + output, output.contains("1234")); 121 122 // Clear all other requests for lskf from the system. 123 String clearOutput = mDevice.executeShellCommand("cmd recovery clear-lskf android"); 124 assertTrue("clear-lskf failed for package android. Output: " + clearOutput, 125 clearOutput.contains("success")); 126 } 127 testTearDown()128 public void testTearDown() throws Exception { 129 // Since there's not a good way to check whether the keyguard is already dismissed, summon 130 // the keyguard and dismiss it. 131 summonKeyguard(); 132 dismissKeyguard(); 133 134 mActivity = launchActivity(getInstrumentation().getTargetContext().getPackageName(), 135 AwareActivity.class, null); 136 mDevice.waitForIdle(); 137 138 // Clear PIN for this user 139 mDevice.executeShellCommand("locksettings clear --old 1234"); 140 mDevice.executeShellCommand("locksettings set-disabled true"); 141 } 142 testLockScreen()143 public void testLockScreen() throws Exception { 144 summonKeyguard(); 145 } 146 testUnlockScreen()147 public void testUnlockScreen() throws Exception { 148 dismissKeyguard(); 149 } 150 doBootCountBefore()151 public void doBootCountBefore() throws Exception { 152 final int thisCount = getBootCount(); 153 mDe.getSharedPreferences(KEY_BOOT, 0).edit().putInt(KEY_BOOT, thisCount).commit(); 154 } 155 doBootCountAfter()156 public void doBootCountAfter() throws Exception { 157 final int lastCount = mDe.getSharedPreferences(KEY_BOOT, 0).getInt(KEY_BOOT, -1); 158 final int thisCount = getBootCount(); 159 assertTrue("Current boot count " + thisCount + " not greater than last " + lastCount, 160 thisCount > lastCount); 161 } 162 testCheckServiceInteraction()163 public void testCheckServiceInteraction() { 164 boolean wrapCalled = 165 mDe.getSharedPreferences(RebootEscrowFakeService.SERVICE_PREFS, 0) 166 .getBoolean("WRAP_CALLED", false); 167 assertTrue(wrapCalled); 168 169 boolean unwrapCalled = 170 mDe.getSharedPreferences(RebootEscrowFakeService.SERVICE_PREFS, 0) 171 .getBoolean("UNWRAP_CALLED", false); 172 assertTrue(unwrapCalled); 173 } 174 testVerifyUnlockedAndDismiss()175 public void testVerifyUnlockedAndDismiss() throws Exception { 176 doBootCountAfter(); 177 assertUnlocked(); 178 dismissKeyguard(); 179 assertUnlocked(); 180 } 181 testVerifyLockedAndDismiss()182 public void testVerifyLockedAndDismiss() throws Exception { 183 doBootCountAfter(); 184 assertLocked(); 185 186 final CountDownLatch latch = new CountDownLatch(1); 187 final BroadcastReceiver receiver = new BroadcastReceiver() { 188 @Override 189 public void onReceive(Context context, Intent intent) { 190 latch.countDown(); 191 } 192 }; 193 mDe.registerReceiver(receiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED)); 194 195 dismissKeyguard(); 196 197 // Dismiss keyguard should have kicked off immediate broadcast 198 assertTrue("USER_UNLOCKED", latch.await(1, TimeUnit.MINUTES)); 199 200 // And we should now be fully unlocked; we run immediately like this to 201 // avoid missing BOOT_COMPLETED due to instrumentation being torn down. 202 assertUnlocked(); 203 } 204 enterTestPin()205 private void enterTestPin() throws Exception { 206 // TODO: change the combination on my luggage 207 208 // Give enough time for the lock screen to show up in the UI. 209 SystemClock.sleep(UNLOCK_SCREEN_START_TIME_SECONDS * 1000); 210 mDevice.waitForIdle(); 211 mDevice.pressKeyCode(KeyEvent.KEYCODE_1); 212 mDevice.pressKeyCode(KeyEvent.KEYCODE_2); 213 mDevice.pressKeyCode(KeyEvent.KEYCODE_3); 214 mDevice.pressKeyCode(KeyEvent.KEYCODE_4); 215 mDevice.waitForIdle(); 216 mDevice.pressEnter(); 217 mDevice.waitForIdle(); 218 219 // TODO(189853309) make sure RebootEscrowManager get the unlock event 220 } 221 dismissKeyguard()222 private void dismissKeyguard() throws Exception { 223 mDevice.waitForIdle(); 224 mDevice.wakeUp(); 225 mDevice.waitForIdle(); 226 227 // Launch activity because KeyguardManager#requestDismissKeyguard() 228 // must be invoked with an activity. 229 mActivity = 230 launchActivity( 231 getInstrumentation().getTargetContext().getPackageName(), 232 AwareActivity.class, 233 null); 234 KeyguardManager keyguardManager = 235 mDe.getSystemService(KeyguardManager.class); 236 keyguardManager.requestDismissKeyguard(mActivity, null); 237 // Close activity to avoid interference with other parts of the test 238 // that also use this activity. 239 mActivity.finish(); 240 mActivity = null; 241 242 enterTestPin(); 243 mDevice.waitForIdle(); 244 mDevice.pressHome(); 245 mDevice.waitForIdle(); 246 } 247 waitFor(String msg, BooleanSupplier waitFor)248 private void waitFor(String msg, BooleanSupplier waitFor) { 249 int retry = 1; 250 do { 251 if (waitFor.getAsBoolean()) { 252 return; 253 } 254 Log.d(TAG, msg + " retry=" + retry); 255 SystemClock.sleep(200); 256 } while (retry++ < 5); 257 if (!waitFor.getAsBoolean()) { 258 fail(msg + " FAILED"); 259 } 260 } 261 summonKeyguard()262 private void summonKeyguard() throws Exception { 263 final KeyguardManager keyguardManager = 264 mDe.getSystemService(KeyguardManager.class); 265 mDevice.pressKeyCode(KeyEvent.KEYCODE_SLEEP); 266 getInstrumentation().getUiAutomation().performGlobalAction( 267 AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN); 268 waitFor( 269 "display to turn off", 270 () -> keyguardManager != null && keyguardManager.isKeyguardLocked()); 271 } 272 assertLocked()273 public void assertLocked() throws Exception { 274 awaitBroadcast(Intent.ACTION_LOCKED_BOOT_COMPLETED); 275 276 assertFalse("CE exists", getTestFile(mCe).exists()); 277 assertTrue("DE exists", getTestFile(mDe).exists()); 278 279 assertFalse("isUserUnlocked", mCe.getSystemService(UserManager.class).isUserUnlocked()); 280 assertFalse("isUserUnlocked", mDe.getSystemService(UserManager.class).isUserUnlocked()); 281 282 assertTrue("AwareProvider", AwareProvider.sCreated); 283 assertFalse("UnawareProvider", UnawareProvider.sCreated); 284 285 assertNotNull("AwareProvider", 286 mPm.resolveContentProvider("com.android.cts.encryptionapp.aware", 0)); 287 assertNull("UnawareProvider", 288 mPm.resolveContentProvider("com.android.cts.encryptionapp.unaware", 0)); 289 290 assertGetAware(true, 0); 291 assertGetAware(true, MATCH_DIRECT_BOOT_AWARE); 292 assertGetAware(false, MATCH_DIRECT_BOOT_UNAWARE); 293 assertGetAware(true, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE); 294 295 assertGetUnaware(false, 0); 296 assertGetUnaware(false, MATCH_DIRECT_BOOT_AWARE); 297 assertGetUnaware(true, MATCH_DIRECT_BOOT_UNAWARE); 298 assertGetUnaware(true, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE); 299 300 assertQuery(1, 0); 301 assertQuery(1, MATCH_DIRECT_BOOT_AWARE); 302 assertQuery(1, MATCH_DIRECT_BOOT_UNAWARE); 303 assertQuery(2, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE); 304 305 if (Environment.isExternalStorageEmulated()) { 306 assertThat(Environment.getExternalStorageState()) 307 .isIn(Arrays.asList(Environment.MEDIA_UNMOUNTED, Environment.MEDIA_REMOVED)); 308 309 final File expected = null; 310 assertEquals(expected, mCe.getExternalCacheDir()); 311 assertEquals(expected, mDe.getExternalCacheDir()); 312 } 313 314 assertViolation( 315 new StrictMode.VmPolicy.Builder().detectImplicitDirectBoot() 316 .penaltyLog().build(), 317 ImplicitDirectBootViolation.class, 318 () -> { 319 final Intent intent = new Intent(Intent.ACTION_DATE_CHANGED); 320 mCe.getPackageManager().queryBroadcastReceivers(intent, 0); 321 }); 322 323 final File ceFile = getTestFile(mCe); 324 assertViolation( 325 new StrictMode.VmPolicy.Builder().detectCredentialProtectedWhileLocked() 326 .penaltyLog().build(), 327 CredentialProtectedWhileLockedViolation.class, 328 ceFile::exists); 329 } 330 assertUnlocked()331 public void assertUnlocked() throws Exception { 332 awaitBroadcast(Intent.ACTION_LOCKED_BOOT_COMPLETED); 333 awaitBroadcast(Intent.ACTION_BOOT_COMPLETED); 334 335 assertTrue("CE exists", getTestFile(mCe).exists()); 336 assertTrue("DE exists", getTestFile(mDe).exists()); 337 338 assertTrue("isUserUnlocked", mCe.getSystemService(UserManager.class).isUserUnlocked()); 339 assertTrue("isUserUnlocked", mDe.getSystemService(UserManager.class).isUserUnlocked()); 340 341 assertTrue("AwareProvider", AwareProvider.sCreated); 342 assertTrue("UnawareProvider", UnawareProvider.sCreated); 343 344 assertNotNull("AwareProvider", 345 mPm.resolveContentProvider("com.android.cts.encryptionapp.aware", 0)); 346 assertNotNull("UnawareProvider", 347 mPm.resolveContentProvider("com.android.cts.encryptionapp.unaware", 0)); 348 349 assertGetAware(true, 0); 350 assertGetAware(true, MATCH_DIRECT_BOOT_AWARE); 351 assertGetAware(false, MATCH_DIRECT_BOOT_UNAWARE); 352 assertGetAware(true, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE); 353 354 assertGetUnaware(true, 0); 355 assertGetUnaware(false, MATCH_DIRECT_BOOT_AWARE); 356 assertGetUnaware(true, MATCH_DIRECT_BOOT_UNAWARE); 357 assertGetUnaware(true, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE); 358 359 assertQuery(2, 0); 360 assertQuery(1, MATCH_DIRECT_BOOT_AWARE); 361 assertQuery(1, MATCH_DIRECT_BOOT_UNAWARE); 362 assertQuery(2, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE); 363 364 if (Environment.isExternalStorageEmulated()) { 365 pollForExternalStorageMountedState(); 366 assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState()); 367 368 final File expected = new File( 369 "/sdcard/Android/data/com.android.cts.encryptionapp/cache"); 370 assertCanonicalEquals(expected, mCe.getExternalCacheDir()); 371 assertCanonicalEquals(expected, mDe.getExternalCacheDir()); 372 } 373 374 assertNoViolation( 375 new StrictMode.VmPolicy.Builder().detectImplicitDirectBoot() 376 .penaltyLog().build(), 377 () -> { 378 final Intent intent = new Intent(Intent.ACTION_DATE_CHANGED); 379 mCe.getPackageManager().queryBroadcastReceivers(intent, 0); 380 }); 381 382 final File ceFile = getTestFile(mCe); 383 assertNoViolation( 384 new StrictMode.VmPolicy.Builder().detectCredentialProtectedWhileLocked() 385 .penaltyLog().build(), 386 ceFile::exists); 387 } 388 pollForExternalStorageMountedState()389 private void pollForExternalStorageMountedState() { 390 for (int i = 0; i < 10; i++) { 391 if (Environment.getExternalStorageState().equalsIgnoreCase(Environment.MEDIA_MOUNTED)) { 392 break; 393 } 394 SystemClock.sleep(500); 395 } 396 } 397 assertQuery(int count, int flags)398 private void assertQuery(int count, int flags) throws Exception { 399 final Intent intent = new Intent(TEST_ACTION); 400 assertEquals("activity", count, mPm.queryIntentActivities(intent, flags).size()); 401 assertEquals("service", count, mPm.queryIntentServices(intent, flags).size()); 402 assertEquals("provider", count, mPm.queryIntentContentProviders(intent, flags).size()); 403 assertEquals("receiver", count, mPm.queryBroadcastReceivers(intent, flags).size()); 404 } 405 assertGetUnaware(boolean visible, int flags)406 private void assertGetUnaware(boolean visible, int flags) throws Exception { 407 assertGet(visible, false, flags); 408 } 409 assertGetAware(boolean visible, int flags)410 private void assertGetAware(boolean visible, int flags) throws Exception { 411 assertGet(visible, true, flags); 412 } 413 assertCanonicalEquals(File expected, File actual)414 private void assertCanonicalEquals(File expected, File actual) throws Exception { 415 assertEquals(expected.getCanonicalFile(), actual.getCanonicalFile()); 416 } 417 buildName(String prefix, String type)418 private ComponentName buildName(String prefix, String type) { 419 return new ComponentName(TEST_PKG, TEST_PKG + "." + prefix + type); 420 } 421 assertGet(boolean visible, boolean aware, int flags)422 private void assertGet(boolean visible, boolean aware, int flags) throws Exception { 423 final String prefix = aware ? "Aware" : "Unaware"; 424 425 ComponentName name; 426 ComponentInfo info; 427 428 name = buildName(prefix, "Activity"); 429 try { 430 info = mPm.getActivityInfo(name, flags); 431 assertTrue(name + " visible", visible); 432 assertEquals(name + " directBootAware", aware, info.directBootAware); 433 } catch (NameNotFoundException e) { 434 assertFalse(name + " visible", visible); 435 } 436 437 name = buildName(prefix, "Service"); 438 try { 439 info = mPm.getServiceInfo(name, flags); 440 assertTrue(name + " visible", visible); 441 assertEquals(name + " directBootAware", aware, info.directBootAware); 442 } catch (NameNotFoundException e) { 443 assertFalse(name + " visible", visible); 444 } 445 446 name = buildName(prefix, "Provider"); 447 try { 448 info = mPm.getProviderInfo(name, flags); 449 assertTrue(name + " visible", visible); 450 assertEquals(name + " directBootAware", aware, info.directBootAware); 451 } catch (NameNotFoundException e) { 452 assertFalse(name + " visible", visible); 453 } 454 455 name = buildName(prefix, "Receiver"); 456 try { 457 info = mPm.getReceiverInfo(name, flags); 458 assertTrue(name + " visible", visible); 459 assertEquals(name + " directBootAware", aware, info.directBootAware); 460 } catch (NameNotFoundException e) { 461 assertFalse(name + " visible", visible); 462 } 463 } 464 getTestFile(Context context)465 private File getTestFile(Context context) { 466 return new File(context.getFilesDir(), "test"); 467 } 468 getBootCount()469 private int getBootCount() throws Exception { 470 return Settings.Global.getInt(mDe.getContentResolver(), Settings.Global.BOOT_COUNT); 471 } 472 queryFileExists(Uri fileUri)473 private boolean queryFileExists(Uri fileUri) { 474 Cursor c = mDe.getContentResolver().query(fileUri, null, null, null, null); 475 if (c == null) { 476 Log.w(TAG, "Couldn't query for file " + fileUri + "; returning false"); 477 return false; 478 } 479 480 c.moveToFirst(); 481 482 int colIndex = c.getColumnIndex("exists"); 483 if (colIndex < 0) { 484 Log.e(TAG, "Column 'exists' does not exist; returning false"); 485 return false; 486 } 487 488 return c.getInt(colIndex) == 1; 489 } 490 awaitBroadcast(String action)491 private void awaitBroadcast(String action) throws Exception { 492 String fileName = getBootCount() + "." + action; 493 Uri fileUri = FILE_INFO_URI.buildUpon().appendPath(fileName).build(); 494 495 TestUtils.waitUntil("Didn't receive broadcast " + action + " for boot " + getBootCount(), 496 BOOT_TIMEOUT_SECONDS, () -> queryFileExists(fileUri)); 497 } 498 499 public interface ThrowingRunnable { run()500 void run() throws Exception; 501 } 502 assertViolation(StrictMode.VmPolicy policy, Class<? extends Violation> expected, ThrowingRunnable r)503 private static void assertViolation(StrictMode.VmPolicy policy, 504 Class<? extends Violation> expected, ThrowingRunnable r) throws Exception { 505 inspectViolation(policy, r, 506 info -> assertThat(info.getViolationClass()).isAssignableTo(expected)); 507 } 508 assertNoViolation(StrictMode.VmPolicy policy, ThrowingRunnable r)509 private static void assertNoViolation(StrictMode.VmPolicy policy, ThrowingRunnable r) 510 throws Exception { 511 inspectViolation(policy, r, 512 info -> assertWithMessage("Unexpected violation").that(info).isNull()); 513 } 514 inspectViolation(StrictMode.VmPolicy policy, ThrowingRunnable violating, Consumer<ViolationInfo> consume)515 private static void inspectViolation(StrictMode.VmPolicy policy, ThrowingRunnable violating, 516 Consumer<ViolationInfo> consume) throws Exception { 517 final LinkedBlockingQueue<ViolationInfo> violations = new LinkedBlockingQueue<>(); 518 StrictMode.setViolationLogger(violations::add); 519 520 final StrictMode.VmPolicy original = StrictMode.getVmPolicy(); 521 try { 522 StrictMode.setVmPolicy(policy); 523 violating.run(); 524 consume.accept(violations.poll(5, TimeUnit.SECONDS)); 525 } finally { 526 StrictMode.setVmPolicy(original); 527 StrictMode.setViolationLogger(null); 528 } 529 } 530 } 531