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