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