• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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