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