1 /*
2  * Copyright (C) 2014 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 package com.android.systemui.statusbar.policy;
17 
18 import android.annotation.Nullable;
19 import android.app.admin.DeviceAdminInfo;
20 import android.app.admin.DevicePolicyManager;
21 import android.app.admin.DevicePolicyManager.DeviceOwnerType;
22 import android.content.BroadcastReceiver;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.content.pm.ApplicationInfo;
28 import android.content.pm.PackageManager;
29 import android.content.pm.PackageManager.NameNotFoundException;
30 import android.content.pm.ResolveInfo;
31 import android.content.pm.UserInfo;
32 import android.graphics.drawable.Drawable;
33 import android.net.ConnectivityManager;
34 import android.net.ConnectivityManager.NetworkCallback;
35 import android.net.Network;
36 import android.net.NetworkRequest;
37 import android.net.VpnManager;
38 import android.os.Handler;
39 import android.os.RemoteException;
40 import android.os.UserHandle;
41 import android.os.UserManager;
42 import android.security.KeyChain;
43 import android.util.ArrayMap;
44 import android.util.Log;
45 import android.util.Pair;
46 import android.util.SparseArray;
47 
48 import androidx.annotation.NonNull;
49 
50 import com.android.internal.annotations.GuardedBy;
51 import com.android.internal.net.LegacyVpnInfo;
52 import com.android.internal.net.VpnConfig;
53 import com.android.systemui.R;
54 import com.android.systemui.broadcast.BroadcastDispatcher;
55 import com.android.systemui.dagger.SysUISingleton;
56 import com.android.systemui.dagger.qualifiers.Background;
57 import com.android.systemui.dagger.qualifiers.Main;
58 import com.android.systemui.dump.DumpManager;
59 import com.android.systemui.settings.UserTracker;
60 
61 import org.xmlpull.v1.XmlPullParserException;
62 
63 import java.io.IOException;
64 import java.io.PrintWriter;
65 import java.util.ArrayList;
66 import java.util.concurrent.Executor;
67 
68 import javax.inject.Inject;
69 
70 /**
71  */
72 @SysUISingleton
73 public class SecurityControllerImpl implements SecurityController {
74 
75     private static final String TAG = "SecurityController";
76     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
77 
78     private static final NetworkRequest REQUEST =
79             new NetworkRequest.Builder().clearCapabilities().build();
80     private static final int NO_NETWORK = -1;
81 
82     private static final String VPN_BRANDED_META_DATA = "com.android.systemui.IS_BRANDED";
83 
84     private static final int CA_CERT_LOADING_RETRY_TIME_IN_MS = 30_000;
85 
86     private final Context mContext;
87     private final UserTracker mUserTracker;
88     private final ConnectivityManager mConnectivityManager;
89     private final VpnManager mVpnManager;
90     private final DevicePolicyManager mDevicePolicyManager;
91     private final PackageManager mPackageManager;
92     private final UserManager mUserManager;
93     private final Executor mMainExecutor;
94     private final Executor mBgExecutor;
95 
96     @GuardedBy("mCallbacks")
97     private final ArrayList<SecurityControllerCallback> mCallbacks = new ArrayList<>();
98 
99     private SparseArray<VpnConfig> mCurrentVpns = new SparseArray<>();
100     private int mCurrentUserId;
101     private int mVpnUserId;
102 
103     // Key: userId, Value: whether the user has CACerts installed
104     // Needs to be cached here since the query has to be asynchronous
105     private ArrayMap<Integer, Boolean> mHasCACerts = new ArrayMap<Integer, Boolean>();
106 
107     private final UserTracker.Callback mUserChangedCallback =
108             new UserTracker.Callback() {
109                 @Override
110                 public void onUserChanged(int newUser, @NonNull Context userContext) {
111                     onUserSwitched(newUser);
112                 }
113             };
114 
115     /**
116      */
117     @Inject
SecurityControllerImpl( Context context, UserTracker userTracker, @Background Handler bgHandler, BroadcastDispatcher broadcastDispatcher, @Main Executor mainExecutor, @Background Executor bgExecutor, DumpManager dumpManager )118     public SecurityControllerImpl(
119             Context context,
120             UserTracker userTracker,
121             @Background Handler bgHandler,
122             BroadcastDispatcher broadcastDispatcher,
123             @Main Executor mainExecutor,
124             @Background Executor bgExecutor,
125             DumpManager dumpManager
126     ) {
127         mContext = context;
128         mUserTracker = userTracker;
129         mDevicePolicyManager = (DevicePolicyManager)
130                 context.getSystemService(Context.DEVICE_POLICY_SERVICE);
131         mConnectivityManager = (ConnectivityManager)
132                 context.getSystemService(Context.CONNECTIVITY_SERVICE);
133         mVpnManager = context.getSystemService(VpnManager.class);
134         mPackageManager = context.getPackageManager();
135         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
136         mMainExecutor = mainExecutor;
137         mBgExecutor = bgExecutor;
138 
139         dumpManager.registerDumpable(getClass().getSimpleName(), this);
140 
141         IntentFilter filter = new IntentFilter();
142         filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
143         filter.addAction(Intent.ACTION_USER_UNLOCKED);
144         broadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, bgHandler,
145                 UserHandle.ALL);
146 
147         // TODO: re-register network callback on user change.
148         mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
149         onUserSwitched(mUserTracker.getUserId());
150         mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
151     }
152 
dump(PrintWriter pw, String[] args)153     public void dump(PrintWriter pw, String[] args) {
154         pw.println("SecurityController state:");
155         pw.print("  mCurrentVpns={");
156         for (int i = 0 ; i < mCurrentVpns.size(); i++) {
157             if (i > 0) {
158                 pw.print(", ");
159             }
160             pw.print(mCurrentVpns.keyAt(i));
161             pw.print('=');
162             pw.print(mCurrentVpns.valueAt(i).user);
163         }
164         pw.println("}");
165     }
166 
167     @Override
isDeviceManaged()168     public boolean isDeviceManaged() {
169         return mDevicePolicyManager.isDeviceManaged();
170     }
171 
172     @Override
getDeviceOwnerName()173     public String getDeviceOwnerName() {
174         return mDevicePolicyManager.getDeviceOwnerNameOnAnyUser();
175     }
176 
177     @Override
hasProfileOwner()178     public boolean hasProfileOwner() {
179         return mDevicePolicyManager.getProfileOwnerAsUser(mCurrentUserId) != null;
180     }
181 
182     @Override
getProfileOwnerName()183     public String getProfileOwnerName() {
184         for (int profileId : mUserManager.getProfileIdsWithDisabled(mCurrentUserId)) {
185             String name = mDevicePolicyManager.getProfileOwnerNameAsUser(profileId);
186             if (name != null) {
187                 return name;
188             }
189         }
190         return null;
191     }
192 
193     @Override
getDeviceOwnerOrganizationName()194     public CharSequence getDeviceOwnerOrganizationName() {
195         return mDevicePolicyManager.getDeviceOwnerOrganizationName();
196     }
197 
198     @Override
getWorkProfileOrganizationName()199     public CharSequence getWorkProfileOrganizationName() {
200         final int profileId = getWorkProfileUserId(mCurrentUserId);
201         if (profileId == UserHandle.USER_NULL) return null;
202         return mDevicePolicyManager.getOrganizationNameForUser(profileId);
203     }
204 
205     @Override
getPrimaryVpnName()206     public String getPrimaryVpnName() {
207         VpnConfig cfg = mCurrentVpns.get(mVpnUserId);
208         if (cfg != null) {
209             return getNameForVpnConfig(cfg, new UserHandle(mVpnUserId));
210         } else {
211             return null;
212         }
213     }
214 
getWorkProfileUserId(int userId)215     private int getWorkProfileUserId(int userId) {
216         for (final UserInfo userInfo : mUserManager.getProfiles(userId)) {
217             if (userInfo.isManagedProfile()) {
218                 return userInfo.id;
219             }
220         }
221         return UserHandle.USER_NULL;
222     }
223 
224     @Override
hasWorkProfile()225     public boolean hasWorkProfile() {
226         return getWorkProfileUserId(mCurrentUserId) != UserHandle.USER_NULL;
227     }
228 
229     @Override
isWorkProfileOn()230     public boolean isWorkProfileOn() {
231         final UserHandle userHandle = UserHandle.of(getWorkProfileUserId(mCurrentUserId));
232         return userHandle != null && !mUserManager.isQuietModeEnabled(userHandle);
233     }
234 
235     @Override
isProfileOwnerOfOrganizationOwnedDevice()236     public boolean isProfileOwnerOfOrganizationOwnedDevice() {
237         return mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile();
238     }
239 
240     @Override
getWorkProfileVpnName()241     public String getWorkProfileVpnName() {
242         final int profileId = getWorkProfileUserId(mVpnUserId);
243         if (profileId == UserHandle.USER_NULL) return null;
244         VpnConfig cfg = mCurrentVpns.get(profileId);
245         if (cfg != null) {
246             return getNameForVpnConfig(cfg, UserHandle.of(profileId));
247         }
248         return null;
249     }
250 
251     @Override
252     @Nullable
getDeviceOwnerComponentOnAnyUser()253     public ComponentName getDeviceOwnerComponentOnAnyUser() {
254         return mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser();
255     }
256 
257     // TODO(b/259908270): remove
258     @Override
259     @DeviceOwnerType
getDeviceOwnerType(@onNull ComponentName admin)260     public int getDeviceOwnerType(@NonNull ComponentName admin) {
261         return mDevicePolicyManager.getDeviceOwnerType(admin);
262     }
263 
264     @Override
isFinancedDevice()265     public boolean isFinancedDevice() {
266         return mDevicePolicyManager.isFinancedDevice();
267     }
268 
269     @Override
isNetworkLoggingEnabled()270     public boolean isNetworkLoggingEnabled() {
271         return mDevicePolicyManager.isNetworkLoggingEnabled(null);
272     }
273 
274     @Override
isVpnEnabled()275     public boolean isVpnEnabled() {
276         for (int profileId : mUserManager.getProfileIdsWithDisabled(mVpnUserId)) {
277             if (mCurrentVpns.get(profileId) != null) {
278                 return true;
279             }
280         }
281         return false;
282     }
283 
284     @Override
isVpnRestricted()285     public boolean isVpnRestricted() {
286         UserHandle currentUser = new UserHandle(mCurrentUserId);
287         return mUserManager.getUserInfo(mCurrentUserId).isRestricted()
288                 || mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN, currentUser);
289     }
290 
291     @Override
isVpnBranded()292     public boolean isVpnBranded() {
293         VpnConfig cfg = mCurrentVpns.get(mVpnUserId);
294         if (cfg == null) {
295             return false;
296         }
297 
298         String packageName = getPackageNameForVpnConfig(cfg);
299         if (packageName == null) {
300             return false;
301         }
302 
303         return isVpnPackageBranded(packageName);
304     }
305 
306     @Override
hasCACertInCurrentUser()307     public boolean hasCACertInCurrentUser() {
308         Boolean hasCACerts = mHasCACerts.get(mCurrentUserId);
309         return hasCACerts != null && hasCACerts.booleanValue();
310     }
311 
312     @Override
hasCACertInWorkProfile()313     public boolean hasCACertInWorkProfile() {
314         int userId = getWorkProfileUserId(mCurrentUserId);
315         if (userId == UserHandle.USER_NULL) return false;
316         Boolean hasCACerts = mHasCACerts.get(userId);
317         return hasCACerts != null && hasCACerts.booleanValue();
318     }
319 
320     @Override
removeCallback(@onNull SecurityControllerCallback callback)321     public void removeCallback(@NonNull SecurityControllerCallback callback) {
322         synchronized (mCallbacks) {
323             if (callback == null) return;
324             if (DEBUG) Log.d(TAG, "removeCallback " + callback);
325             mCallbacks.remove(callback);
326         }
327     }
328 
329     @Override
addCallback(@onNull SecurityControllerCallback callback)330     public void addCallback(@NonNull SecurityControllerCallback callback) {
331         synchronized (mCallbacks) {
332             if (callback == null || mCallbacks.contains(callback)) return;
333             if (DEBUG) Log.d(TAG, "addCallback " + callback);
334             mCallbacks.add(callback);
335         }
336     }
337 
338     @Override
onUserSwitched(int newUserId)339     public void onUserSwitched(int newUserId) {
340         mCurrentUserId = newUserId;
341         final UserInfo newUserInfo = mUserManager.getUserInfo(newUserId);
342         if (newUserInfo.isRestricted()) {
343             // VPN for a restricted profile is routed through its owner user
344             mVpnUserId = newUserInfo.restrictedProfileParentId;
345         } else {
346             mVpnUserId = mCurrentUserId;
347         }
348         fireCallbacks();
349     }
350 
351     @Override
isParentalControlsEnabled()352     public boolean isParentalControlsEnabled() {
353         return getProfileOwnerOrDeviceOwnerSupervisionComponent() != null;
354     }
355 
356     @Override
getDeviceAdminInfo()357     public DeviceAdminInfo getDeviceAdminInfo() {
358         return getDeviceAdminInfo(getProfileOwnerOrDeviceOwnerComponent());
359     }
360 
361     @Override
getIcon(DeviceAdminInfo info)362     public Drawable getIcon(DeviceAdminInfo info) {
363         return (info == null) ? null : info.loadIcon(mPackageManager);
364     }
365 
366     @Override
getLabel(DeviceAdminInfo info)367     public CharSequence getLabel(DeviceAdminInfo info) {
368         return (info == null) ? null : info.loadLabel(mPackageManager);
369     }
370 
getProfileOwnerOrDeviceOwnerSupervisionComponent()371     private ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent() {
372         UserHandle currentUser = new UserHandle(mCurrentUserId);
373         return mDevicePolicyManager
374                .getProfileOwnerOrDeviceOwnerSupervisionComponent(currentUser);
375     }
376 
377     // Returns the ComponentName of the current DO/PO. Right now it only checks the supervision
378     // component but can be changed to check for other DO/POs. This change would make getIcon()
379     // and getLabel() work for all admins.
getProfileOwnerOrDeviceOwnerComponent()380     private ComponentName getProfileOwnerOrDeviceOwnerComponent() {
381         return getProfileOwnerOrDeviceOwnerSupervisionComponent();
382     }
383 
getDeviceAdminInfo(ComponentName componentName)384     private DeviceAdminInfo getDeviceAdminInfo(ComponentName componentName) {
385         try {
386             ResolveInfo resolveInfo = new ResolveInfo();
387             resolveInfo.activityInfo = mPackageManager.getReceiverInfo(componentName,
388                     PackageManager.GET_META_DATA);
389             return new DeviceAdminInfo(mContext, resolveInfo);
390         } catch (NameNotFoundException | XmlPullParserException | IOException e) {
391             return null;
392         }
393     }
394 
refreshCACerts(int userId)395     private void refreshCACerts(int userId) {
396         mBgExecutor.execute(() -> {
397             Pair<Integer, Boolean> idWithCert = null;
398             try (KeyChain.KeyChainConnection conn = KeyChain.bindAsUser(mContext,
399                     UserHandle.of(userId))) {
400                 boolean hasCACerts = !(conn.getService().getUserCaAliases().getList().isEmpty());
401                 idWithCert = new Pair<Integer, Boolean>(userId, hasCACerts);
402             } catch (RemoteException | InterruptedException | AssertionError e) {
403                 Log.i(TAG, "failed to get CA certs", e);
404                 idWithCert = new Pair<Integer, Boolean>(userId, null);
405             } finally {
406                 if (DEBUG) Log.d(TAG, "Refreshing CA Certs " + idWithCert);
407                 if (idWithCert != null && idWithCert.second != null) {
408                     mHasCACerts.put(idWithCert.first, idWithCert.second);
409                     fireCallbacks();
410                 }
411             }
412         });
413     }
414 
getNameForVpnConfig(VpnConfig cfg, UserHandle user)415     private String getNameForVpnConfig(VpnConfig cfg, UserHandle user) {
416         if (cfg.legacy) {
417             return mContext.getString(R.string.legacy_vpn_name);
418         }
419         // The package name for an active VPN is stored in the 'user' field of its VpnConfig
420         final String vpnPackage = cfg.user;
421         try {
422             Context userContext = mContext.createPackageContextAsUser(mContext.getPackageName(),
423                     0 /* flags */, user);
424             return VpnConfig.getVpnLabel(userContext, vpnPackage).toString();
425         } catch (NameNotFoundException nnfe) {
426             Log.e(TAG, "Package " + vpnPackage + " is not present", nnfe);
427             return null;
428         }
429     }
430 
fireCallbacks()431     private void fireCallbacks() {
432         synchronized (mCallbacks) {
433             for (SecurityControllerCallback callback : mCallbacks) {
434                 callback.onStateChanged();
435             }
436         }
437     }
438 
updateState()439     private void updateState() {
440         // Find all users with an active VPN
441         SparseArray<VpnConfig> vpns = new SparseArray<>();
442         for (UserInfo user : mUserManager.getUsers()) {
443             VpnConfig cfg = mVpnManager.getVpnConfig(user.id);
444             if (cfg == null) {
445                 continue;
446             } else if (cfg.legacy) {
447                 // Legacy VPNs should do nothing if the network is disconnected. Third-party
448                 // VPN warnings need to continue as traffic can still go to the app.
449                 LegacyVpnInfo legacyVpn = mVpnManager.getLegacyVpnInfo(user.id);
450                 if (legacyVpn == null || legacyVpn.state != LegacyVpnInfo.STATE_CONNECTED) {
451                     continue;
452                 }
453             }
454             vpns.put(user.id, cfg);
455         }
456         mCurrentVpns = vpns;
457     }
458 
getPackageNameForVpnConfig(VpnConfig cfg)459     private String getPackageNameForVpnConfig(VpnConfig cfg) {
460         if (cfg.legacy) {
461             return null;
462         }
463         return cfg.user;
464     }
465 
isVpnPackageBranded(String packageName)466     private boolean isVpnPackageBranded(String packageName) {
467         boolean isBranded;
468         try {
469             ApplicationInfo info = mPackageManager.getApplicationInfo(packageName,
470                 PackageManager.GET_META_DATA);
471             if (info == null || info.metaData == null || !info.isSystemApp()) {
472                 return false;
473             }
474             isBranded = info.metaData.getBoolean(VPN_BRANDED_META_DATA, false);
475         } catch (NameNotFoundException e) {
476             return false;
477         }
478         return isBranded;
479     }
480 
481     private final NetworkCallback mNetworkCallback = new NetworkCallback() {
482         @Override
483         public void onAvailable(Network network) {
484             if (DEBUG) Log.d(TAG, "onAvailable " + network.getNetId());
485             updateState();
486             fireCallbacks();
487         };
488 
489         // TODO Find another way to receive VPN lost.  This may be delayed depending on
490         // how long the VPN connection is held on to.
491         @Override
492         public void onLost(Network network) {
493             if (DEBUG) Log.d(TAG, "onLost " + network.getNetId());
494             updateState();
495             fireCallbacks();
496         };
497     };
498 
499     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
500         @Override public void onReceive(Context context, Intent intent) {
501             if (KeyChain.ACTION_TRUST_STORE_CHANGED.equals(intent.getAction())) {
502                 refreshCACerts(getSendingUserId());
503             } else if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
504                 int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
505                 if (userId != UserHandle.USER_NULL) refreshCACerts(userId);
506             }
507         }
508     };
509 }
510