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