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