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