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.content.BroadcastReceiver;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.pm.ComponentInfo;
31 import android.content.pm.PackageManager;
32 import android.content.pm.PackageManager.NameNotFoundException;
33 import android.os.Environment;
34 import android.os.StrictMode;
35 import android.os.StrictMode.ViolationInfo;
36 import android.os.SystemClock;
37 import android.os.UserManager;
38 import android.os.strictmode.CredentialProtectedWhileLockedViolation;
39 import android.os.strictmode.ImplicitDirectBootViolation;
40 import android.os.strictmode.Violation;
41 import android.provider.Settings;
42 import android.support.test.uiautomator.UiDevice;
43 import android.test.InstrumentationTestCase;
44 import android.text.format.DateUtils;
45 import android.util.Log;
46 import android.view.KeyEvent;
47 
48 import java.io.File;
49 import java.util.concurrent.CountDownLatch;
50 import java.util.concurrent.LinkedBlockingQueue;
51 import java.util.concurrent.TimeUnit;
52 import java.util.function.Consumer;
53 
54 public class EncryptionAppTest extends InstrumentationTestCase {
55     private static final String TAG = "EncryptionAppTest";
56 
57     private static final long TIMEOUT = 10 * DateUtils.SECOND_IN_MILLIS;
58 
59     private static final String KEY_BOOT = "boot";
60 
61     private static final String TEST_PKG = "com.android.cts.encryptionapp";
62     private static final String TEST_ACTION = "com.android.cts.encryptionapp.TEST";
63 
64     private static final String OTHER_PKG = "com.android.cts.splitapp";
65 
66     private Context mCe;
67     private Context mDe;
68     private PackageManager mPm;
69 
70     private UiDevice mDevice;
71     private AwareActivity mActivity;
72 
73     @Override
setUp()74     public void setUp() throws Exception {
75         super.setUp();
76 
77         mCe = getInstrumentation().getContext();
78         mDe = mCe.createDeviceProtectedStorageContext();
79         mPm = mCe.getPackageManager();
80 
81         mDevice = UiDevice.getInstance(getInstrumentation());
82         assertNotNull(mDevice);
83     }
84 
85     @Override
tearDown()86     public void tearDown() throws Exception {
87         super.tearDown();
88 
89         if (mActivity != null) {
90             mActivity.finish();
91         }
92     }
93 
testSetUp()94     public void testSetUp() throws Exception {
95         // Write both CE/DE data for ourselves
96         assertTrue("CE file", getTestFile(mCe).createNewFile());
97         assertTrue("DE file", getTestFile(mDe).createNewFile());
98 
99         doBootCountBefore();
100 
101         mActivity = launchActivity(getInstrumentation().getTargetContext().getPackageName(),
102                 AwareActivity.class, null);
103         mDevice.waitForIdle();
104 
105         // Set a PIN for this user
106         mDevice.executeShellCommand("settings put global require_password_to_decrypt 0");
107         mDevice.executeShellCommand("locksettings set-disabled false");
108         mDevice.executeShellCommand("locksettings set-pin 12345");
109     }
110 
testTearDown()111     public void testTearDown() throws Exception {
112         // Just in case, always try tearing down keyguard
113         dismissKeyguard();
114 
115         mActivity = launchActivity(getInstrumentation().getTargetContext().getPackageName(),
116                 AwareActivity.class, null);
117         mDevice.waitForIdle();
118 
119         // Clear PIN for this user
120         mDevice.executeShellCommand("locksettings clear --old 12345");
121         mDevice.executeShellCommand("locksettings set-disabled true");
122         mDevice.executeShellCommand("settings delete global require_password_to_decrypt");
123     }
124 
doBootCountBefore()125     public void doBootCountBefore() throws Exception {
126         final int thisCount = getBootCount();
127         mDe.getSharedPreferences(KEY_BOOT, 0).edit().putInt(KEY_BOOT, thisCount).commit();
128     }
129 
doBootCountAfter()130     public void doBootCountAfter() throws Exception {
131         final int lastCount = mDe.getSharedPreferences(KEY_BOOT, 0).getInt(KEY_BOOT, -1);
132         final int thisCount = getBootCount();
133         assertTrue("Current boot count " + thisCount + " not greater than last " + lastCount,
134                 thisCount > lastCount);
135     }
136 
testVerifyUnlockedAndDismiss()137     public void testVerifyUnlockedAndDismiss() throws Exception {
138         doBootCountAfter();
139         assertUnlocked();
140         dismissKeyguard();
141         assertUnlocked();
142     }
143 
testVerifyLockedAndDismiss()144     public void testVerifyLockedAndDismiss() throws Exception {
145         doBootCountAfter();
146         assertLocked();
147 
148         final CountDownLatch latch = new CountDownLatch(1);
149         final BroadcastReceiver receiver = new BroadcastReceiver() {
150             @Override
151             public void onReceive(Context context, Intent intent) {
152                 latch.countDown();
153             }
154         };
155         mDe.registerReceiver(receiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
156 
157         dismissKeyguard();
158 
159         // Dismiss keyguard should have kicked off immediate broadcast
160         assertTrue("USER_UNLOCKED", latch.await(1, TimeUnit.MINUTES));
161 
162         // And we should now be fully unlocked; we run immediately like this to
163         // avoid missing BOOT_COMPLETED due to instrumentation being torn down.
164         assertUnlocked();
165     }
166 
enterTestPin()167     private void enterTestPin() throws Exception {
168         // TODO: change the combination on my luggage
169         mDevice.waitForIdle();
170         mDevice.pressKeyCode(KeyEvent.KEYCODE_1);
171         mDevice.pressKeyCode(KeyEvent.KEYCODE_2);
172         mDevice.pressKeyCode(KeyEvent.KEYCODE_3);
173         mDevice.pressKeyCode(KeyEvent.KEYCODE_4);
174         mDevice.pressKeyCode(KeyEvent.KEYCODE_5);
175         mDevice.waitForIdle();
176         mDevice.pressEnter();
177         mDevice.waitForIdle();
178     }
179 
dismissKeyguard()180     private void dismissKeyguard() throws Exception {
181         mDevice.wakeUp();
182         mDevice.waitForIdle();
183         mDevice.pressMenu();
184         mDevice.waitForIdle();
185         enterTestPin();
186         mDevice.waitForIdle();
187         mDevice.pressHome();
188         mDevice.waitForIdle();
189     }
190 
assertLocked()191     public void assertLocked() throws Exception {
192         awaitBroadcast(Intent.ACTION_LOCKED_BOOT_COMPLETED);
193 
194         assertFalse("CE exists", getTestFile(mCe).exists());
195         assertTrue("DE exists", getTestFile(mDe).exists());
196 
197         assertFalse("isUserUnlocked", mCe.getSystemService(UserManager.class).isUserUnlocked());
198         assertFalse("isUserUnlocked", mDe.getSystemService(UserManager.class).isUserUnlocked());
199 
200         assertTrue("AwareProvider", AwareProvider.sCreated);
201         assertFalse("UnawareProvider", UnawareProvider.sCreated);
202 
203         assertNotNull("AwareProvider",
204                 mPm.resolveContentProvider("com.android.cts.encryptionapp.aware", 0));
205         assertNull("UnawareProvider",
206                 mPm.resolveContentProvider("com.android.cts.encryptionapp.unaware", 0));
207 
208         assertGetAware(true, 0);
209         assertGetAware(true, MATCH_DIRECT_BOOT_AWARE);
210         assertGetAware(false, MATCH_DIRECT_BOOT_UNAWARE);
211         assertGetAware(true, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE);
212 
213         assertGetUnaware(false, 0);
214         assertGetUnaware(false, MATCH_DIRECT_BOOT_AWARE);
215         assertGetUnaware(true, MATCH_DIRECT_BOOT_UNAWARE);
216         assertGetUnaware(true, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE);
217 
218         assertQuery(1, 0);
219         assertQuery(1, MATCH_DIRECT_BOOT_AWARE);
220         assertQuery(1, MATCH_DIRECT_BOOT_UNAWARE);
221         assertQuery(2, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE);
222 
223         if (Environment.isExternalStorageEmulated()) {
224             assertEquals(Environment.MEDIA_UNMOUNTED, Environment.getExternalStorageState());
225 
226             final File expected = null;
227             assertEquals(expected, mCe.getExternalCacheDir());
228             assertEquals(expected, mDe.getExternalCacheDir());
229         }
230 
231         assertViolation(
232                 new StrictMode.VmPolicy.Builder().detectImplicitDirectBoot()
233                         .penaltyLog().build(),
234                 ImplicitDirectBootViolation.class,
235                 () -> {
236                     final Intent intent = new Intent(Intent.ACTION_DATE_CHANGED);
237                     mCe.getPackageManager().queryBroadcastReceivers(intent, 0);
238                 });
239 
240         final File ceFile = getTestFile(mCe);
241         assertViolation(
242                 new StrictMode.VmPolicy.Builder().detectCredentialProtectedWhileLocked()
243                         .penaltyLog().build(),
244                 CredentialProtectedWhileLockedViolation.class,
245                 ceFile::exists);
246     }
247 
assertUnlocked()248     public void assertUnlocked() throws Exception {
249         awaitBroadcast(Intent.ACTION_LOCKED_BOOT_COMPLETED);
250         awaitBroadcast(Intent.ACTION_BOOT_COMPLETED);
251 
252         assertTrue("CE exists", getTestFile(mCe).exists());
253         assertTrue("DE exists", getTestFile(mDe).exists());
254 
255         assertTrue("isUserUnlocked", mCe.getSystemService(UserManager.class).isUserUnlocked());
256         assertTrue("isUserUnlocked", mDe.getSystemService(UserManager.class).isUserUnlocked());
257 
258         assertTrue("AwareProvider", AwareProvider.sCreated);
259         assertTrue("UnawareProvider", UnawareProvider.sCreated);
260 
261         assertNotNull("AwareProvider",
262                 mPm.resolveContentProvider("com.android.cts.encryptionapp.aware", 0));
263         assertNotNull("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(true, 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(2, 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             assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
283 
284             final File expected = new File(
285                     "/sdcard/Android/data/com.android.cts.encryptionapp/cache");
286             assertCanonicalEquals(expected, mCe.getExternalCacheDir());
287             assertCanonicalEquals(expected, mDe.getExternalCacheDir());
288         }
289 
290         assertNoViolation(
291                 new StrictMode.VmPolicy.Builder().detectImplicitDirectBoot()
292                         .penaltyLog().build(),
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         assertNoViolation(
300                 new StrictMode.VmPolicy.Builder().detectCredentialProtectedWhileLocked()
301                         .penaltyLog().build(),
302                 ceFile::exists);
303     }
304 
assertQuery(int count, int flags)305     private void assertQuery(int count, int flags) throws Exception {
306         final Intent intent = new Intent(TEST_ACTION);
307         assertEquals("activity", count, mPm.queryIntentActivities(intent, flags).size());
308         assertEquals("service", count, mPm.queryIntentServices(intent, flags).size());
309         assertEquals("provider", count, mPm.queryIntentContentProviders(intent, flags).size());
310         assertEquals("receiver", count, mPm.queryBroadcastReceivers(intent, flags).size());
311     }
312 
assertGetUnaware(boolean visible, int flags)313     private void assertGetUnaware(boolean visible, int flags) throws Exception {
314         assertGet(visible, false, flags);
315     }
316 
assertGetAware(boolean visible, int flags)317     private void assertGetAware(boolean visible, int flags) throws Exception {
318         assertGet(visible, true, flags);
319     }
320 
assertCanonicalEquals(File expected, File actual)321     private void assertCanonicalEquals(File expected, File actual) throws Exception {
322         assertEquals(expected.getCanonicalFile(), actual.getCanonicalFile());
323     }
324 
buildName(String prefix, String type)325     private ComponentName buildName(String prefix, String type) {
326         return new ComponentName(TEST_PKG, TEST_PKG + "." + prefix + type);
327     }
328 
assertGet(boolean visible, boolean aware, int flags)329     private void assertGet(boolean visible, boolean aware, int flags) throws Exception {
330         final String prefix = aware ? "Aware" : "Unaware";
331 
332         ComponentName name;
333         ComponentInfo info;
334 
335         name = buildName(prefix, "Activity");
336         try {
337             info = mPm.getActivityInfo(name, flags);
338             assertTrue(name + " visible", visible);
339             assertEquals(name + " directBootAware", aware, info.directBootAware);
340         } catch (NameNotFoundException e) {
341             assertFalse(name + " visible", visible);
342         }
343 
344         name = buildName(prefix, "Service");
345         try {
346             info = mPm.getServiceInfo(name, flags);
347             assertTrue(name + " visible", visible);
348             assertEquals(name + " directBootAware", aware, info.directBootAware);
349         } catch (NameNotFoundException e) {
350             assertFalse(name + " visible", visible);
351         }
352 
353         name = buildName(prefix, "Provider");
354         try {
355             info = mPm.getProviderInfo(name, flags);
356             assertTrue(name + " visible", visible);
357             assertEquals(name + " directBootAware", aware, info.directBootAware);
358         } catch (NameNotFoundException e) {
359             assertFalse(name + " visible", visible);
360         }
361 
362         name = buildName(prefix, "Receiver");
363         try {
364             info = mPm.getReceiverInfo(name, flags);
365             assertTrue(name + " visible", visible);
366             assertEquals(name + " directBootAware", aware, info.directBootAware);
367         } catch (NameNotFoundException e) {
368             assertFalse(name + " visible", visible);
369         }
370     }
371 
getTestFile(Context context)372     private File getTestFile(Context context) {
373         return new File(context.getFilesDir(), "test");
374     }
375 
getBootCount()376     private int getBootCount() throws Exception {
377         return Settings.Global.getInt(mDe.getContentResolver(), Settings.Global.BOOT_COUNT);
378     }
379 
awaitBroadcast(String action)380     private void awaitBroadcast(String action) throws Exception {
381         final Context otherContext = mDe.createPackageContext(OTHER_PKG, 0)
382                 .createDeviceProtectedStorageContext();
383         final File probe = new File(otherContext.getFilesDir(),
384                 getBootCount() + "." + action);
385         for (int i = 0; i < 150; i++) {
386             Log.d(TAG, "Waiting for " + probe + "...");
387             if (probe.exists()) {
388                 return;
389             }
390             SystemClock.sleep(1000);
391         }
392         throw new AssertionError("Failed to find " + probe);
393     }
394 
395     public interface ThrowingRunnable {
run()396         void run() throws Exception;
397     }
398 
assertViolation(StrictMode.VmPolicy policy, Class<? extends Violation> expected, ThrowingRunnable r)399     private static void assertViolation(StrictMode.VmPolicy policy,
400             Class<? extends Violation> expected, ThrowingRunnable r) throws Exception {
401         inspectViolation(policy, r,
402                 info -> assertThat(info.getViolationClass()).isAssignableTo(expected));
403     }
404 
assertNoViolation(StrictMode.VmPolicy policy, ThrowingRunnable r)405     private static void assertNoViolation(StrictMode.VmPolicy policy, ThrowingRunnable r)
406             throws Exception {
407         inspectViolation(policy, r,
408                 info -> assertWithMessage("Unexpected violation").that(info).isNull());
409     }
410 
inspectViolation(StrictMode.VmPolicy policy, ThrowingRunnable violating, Consumer<ViolationInfo> consume)411     private static void inspectViolation(StrictMode.VmPolicy policy, ThrowingRunnable violating,
412             Consumer<ViolationInfo> consume) throws Exception {
413         final LinkedBlockingQueue<ViolationInfo> violations = new LinkedBlockingQueue<>();
414         StrictMode.setViolationLogger(violations::add);
415 
416         final StrictMode.VmPolicy original = StrictMode.getVmPolicy();
417         try {
418             StrictMode.setVmPolicy(policy);
419             violating.run();
420             consume.accept(violations.poll(5, TimeUnit.SECONDS));
421         } finally {
422             StrictMode.setVmPolicy(original);
423             StrictMode.setViolationLogger(null);
424         }
425     }
426 }
427