Home | History | Annotate | Download | only in policy
      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.app.ActivityManager;
     19 import android.app.admin.DevicePolicyManager;
     20 import android.content.BroadcastReceiver;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.IntentFilter;
     24 import android.content.pm.ApplicationInfo;
     25 import android.content.pm.PackageManager;
     26 import android.content.pm.PackageManager.NameNotFoundException;
     27 import android.content.pm.UserInfo;
     28 import android.net.ConnectivityManager;
     29 import android.net.ConnectivityManager.NetworkCallback;
     30 import android.net.IConnectivityManager;
     31 import android.net.Network;
     32 import android.net.NetworkCapabilities;
     33 import android.net.NetworkRequest;
     34 import android.os.AsyncTask;
     35 import android.os.Handler;
     36 import android.os.RemoteException;
     37 import android.os.ServiceManager;
     38 import android.os.UserHandle;
     39 import android.os.UserManager;
     40 import android.security.KeyChain;
     41 import android.security.KeyChain.KeyChainConnection;
     42 import android.util.ArrayMap;
     43 import android.util.Log;
     44 import android.util.Pair;
     45 import android.util.SparseArray;
     46 
     47 import com.android.internal.annotations.GuardedBy;
     48 import com.android.internal.net.LegacyVpnInfo;
     49 import com.android.internal.net.VpnConfig;
     50 import com.android.systemui.Dependency;
     51 import com.android.systemui.R;
     52 import com.android.systemui.settings.CurrentUserTracker;
     53 
     54 import java.io.FileDescriptor;
     55 import java.io.PrintWriter;
     56 import java.util.ArrayList;
     57 
     58 public class SecurityControllerImpl extends CurrentUserTracker implements SecurityController {
     59 
     60     private static final String TAG = "SecurityController";
     61     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     62 
     63     private static final NetworkRequest REQUEST = new NetworkRequest.Builder()
     64             .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
     65             .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
     66             .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
     67             .setUids(null)
     68             .build();
     69     private static final int NO_NETWORK = -1;
     70 
     71     private static final String VPN_BRANDED_META_DATA = "com.android.systemui.IS_BRANDED";
     72 
     73     private static final int CA_CERT_LOADING_RETRY_TIME_IN_MS = 30_000;
     74 
     75     private final Context mContext;
     76     private final ConnectivityManager mConnectivityManager;
     77     private final IConnectivityManager mConnectivityManagerService;
     78     private final DevicePolicyManager mDevicePolicyManager;
     79     private final PackageManager mPackageManager;
     80     private final UserManager mUserManager;
     81 
     82     @GuardedBy("mCallbacks")
     83     private final ArrayList<SecurityControllerCallback> mCallbacks = new ArrayList<>();
     84 
     85     private SparseArray<VpnConfig> mCurrentVpns = new SparseArray<>();
     86     private int mCurrentUserId;
     87     private int mVpnUserId;
     88 
     89     // Key: userId, Value: whether the user has CACerts installed
     90     // Needs to be cached here since the query has to be asynchronous
     91     private ArrayMap<Integer, Boolean> mHasCACerts = new ArrayMap<Integer, Boolean>();
     92 
     93     public SecurityControllerImpl(Context context) {
     94         this(context, null);
     95     }
     96 
     97     public SecurityControllerImpl(Context context, SecurityControllerCallback callback) {
     98         super(context);
     99         mContext = context;
    100         mDevicePolicyManager = (DevicePolicyManager)
    101                 context.getSystemService(Context.DEVICE_POLICY_SERVICE);
    102         mConnectivityManager = (ConnectivityManager)
    103                 context.getSystemService(Context.CONNECTIVITY_SERVICE);
    104         mConnectivityManagerService = IConnectivityManager.Stub.asInterface(
    105                 ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
    106         mPackageManager = context.getPackageManager();
    107         mUserManager = (UserManager)
    108                 context.getSystemService(Context.USER_SERVICE);
    109 
    110         addCallback(callback);
    111 
    112         IntentFilter filter = new IntentFilter();
    113         filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
    114         context.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null,
    115                 new Handler(Dependency.get(Dependency.BG_LOOPER)));
    116 
    117         // TODO: re-register network callback on user change.
    118         mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
    119         onUserSwitched(ActivityManager.getCurrentUser());
    120         startTracking();
    121     }
    122 
    123     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    124         pw.println("SecurityController state:");
    125         pw.print("  mCurrentVpns={");
    126         for (int i = 0 ; i < mCurrentVpns.size(); i++) {
    127             if (i > 0) {
    128                 pw.print(", ");
    129             }
    130             pw.print(mCurrentVpns.keyAt(i));
    131             pw.print('=');
    132             pw.print(mCurrentVpns.valueAt(i).user);
    133         }
    134         pw.println("}");
    135     }
    136 
    137     @Override
    138     public boolean isDeviceManaged() {
    139         return mDevicePolicyManager.isDeviceManaged();
    140     }
    141 
    142     @Override
    143     public String getDeviceOwnerName() {
    144         return mDevicePolicyManager.getDeviceOwnerNameOnAnyUser();
    145     }
    146 
    147     @Override
    148     public boolean hasProfileOwner() {
    149         return mDevicePolicyManager.getProfileOwnerAsUser(mCurrentUserId) != null;
    150     }
    151 
    152     @Override
    153     public String getProfileOwnerName() {
    154         for (int profileId : mUserManager.getProfileIdsWithDisabled(mCurrentUserId)) {
    155             String name = mDevicePolicyManager.getProfileOwnerNameAsUser(profileId);
    156             if (name != null) {
    157                 return name;
    158             }
    159         }
    160         return null;
    161     }
    162 
    163     @Override
    164     public CharSequence getDeviceOwnerOrganizationName() {
    165         return mDevicePolicyManager.getDeviceOwnerOrganizationName();
    166     }
    167 
    168     @Override
    169     public CharSequence getWorkProfileOrganizationName() {
    170         final int profileId = getWorkProfileUserId(mCurrentUserId);
    171         if (profileId == UserHandle.USER_NULL) return null;
    172         return mDevicePolicyManager.getOrganizationNameForUser(profileId);
    173     }
    174 
    175     @Override
    176     public String getPrimaryVpnName() {
    177         VpnConfig cfg = mCurrentVpns.get(mVpnUserId);
    178         if (cfg != null) {
    179             return getNameForVpnConfig(cfg, new UserHandle(mVpnUserId));
    180         } else {
    181             return null;
    182         }
    183     }
    184 
    185     private int getWorkProfileUserId(int userId) {
    186         for (final UserInfo userInfo : mUserManager.getProfiles(userId)) {
    187             if (userInfo.isManagedProfile()) {
    188                 return userInfo.id;
    189             }
    190         }
    191         return UserHandle.USER_NULL;
    192     }
    193 
    194     @Override
    195     public boolean hasWorkProfile() {
    196         return getWorkProfileUserId(mCurrentUserId) != UserHandle.USER_NULL;
    197     }
    198 
    199     @Override
    200     public String getWorkProfileVpnName() {
    201         final int profileId = getWorkProfileUserId(mVpnUserId);
    202         if (profileId == UserHandle.USER_NULL) return null;
    203         VpnConfig cfg = mCurrentVpns.get(profileId);
    204         if (cfg != null) {
    205             return getNameForVpnConfig(cfg, UserHandle.of(profileId));
    206         }
    207         return null;
    208     }
    209 
    210     @Override
    211     public boolean isNetworkLoggingEnabled() {
    212         return mDevicePolicyManager.isNetworkLoggingEnabled(null);
    213     }
    214 
    215     @Override
    216     public boolean isVpnEnabled() {
    217         for (int profileId : mUserManager.getProfileIdsWithDisabled(mVpnUserId)) {
    218             if (mCurrentVpns.get(profileId) != null) {
    219                 return true;
    220             }
    221         }
    222         return false;
    223     }
    224 
    225     @Override
    226     public boolean isVpnRestricted() {
    227         UserHandle currentUser = new UserHandle(mCurrentUserId);
    228         return mUserManager.getUserInfo(mCurrentUserId).isRestricted()
    229                 || mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN, currentUser);
    230     }
    231 
    232     @Override
    233     public boolean isVpnBranded() {
    234         VpnConfig cfg = mCurrentVpns.get(mVpnUserId);
    235         if (cfg == null) {
    236             return false;
    237         }
    238 
    239         String packageName = getPackageNameForVpnConfig(cfg);
    240         if (packageName == null) {
    241             return false;
    242         }
    243 
    244         return isVpnPackageBranded(packageName);
    245     }
    246 
    247     @Override
    248     public boolean hasCACertInCurrentUser() {
    249         Boolean hasCACerts = mHasCACerts.get(mCurrentUserId);
    250         return hasCACerts != null && hasCACerts.booleanValue();
    251     }
    252 
    253     @Override
    254     public boolean hasCACertInWorkProfile() {
    255         int userId = getWorkProfileUserId(mCurrentUserId);
    256         if (userId == UserHandle.USER_NULL) return false;
    257         Boolean hasCACerts = mHasCACerts.get(userId);
    258         return hasCACerts != null && hasCACerts.booleanValue();
    259     }
    260 
    261     @Override
    262     public void removeCallback(SecurityControllerCallback callback) {
    263         synchronized (mCallbacks) {
    264             if (callback == null) return;
    265             if (DEBUG) Log.d(TAG, "removeCallback " + callback);
    266             mCallbacks.remove(callback);
    267         }
    268     }
    269 
    270     @Override
    271     public void addCallback(SecurityControllerCallback callback) {
    272         synchronized (mCallbacks) {
    273             if (callback == null || mCallbacks.contains(callback)) return;
    274             if (DEBUG) Log.d(TAG, "addCallback " + callback);
    275             mCallbacks.add(callback);
    276         }
    277     }
    278 
    279     @Override
    280     public void onUserSwitched(int newUserId) {
    281         mCurrentUserId = newUserId;
    282         final UserInfo newUserInfo = mUserManager.getUserInfo(newUserId);
    283         if (newUserInfo.isRestricted()) {
    284             // VPN for a restricted profile is routed through its owner user
    285             mVpnUserId = newUserInfo.restrictedProfileParentId;
    286         } else {
    287             mVpnUserId = mCurrentUserId;
    288         }
    289         refreshCACerts();
    290         fireCallbacks();
    291     }
    292 
    293     private void refreshCACerts() {
    294         new CACertLoader().execute(mCurrentUserId);
    295         int workProfileId = getWorkProfileUserId(mCurrentUserId);
    296         if (workProfileId != UserHandle.USER_NULL) new CACertLoader().execute(workProfileId);
    297     }
    298 
    299     private String getNameForVpnConfig(VpnConfig cfg, UserHandle user) {
    300         if (cfg.legacy) {
    301             return mContext.getString(R.string.legacy_vpn_name);
    302         }
    303         // The package name for an active VPN is stored in the 'user' field of its VpnConfig
    304         final String vpnPackage = cfg.user;
    305         try {
    306             Context userContext = mContext.createPackageContextAsUser(mContext.getPackageName(),
    307                     0 /* flags */, user);
    308             return VpnConfig.getVpnLabel(userContext, vpnPackage).toString();
    309         } catch (NameNotFoundException nnfe) {
    310             Log.e(TAG, "Package " + vpnPackage + " is not present", nnfe);
    311             return null;
    312         }
    313     }
    314 
    315     private void fireCallbacks() {
    316         synchronized (mCallbacks) {
    317             for (SecurityControllerCallback callback : mCallbacks) {
    318                 callback.onStateChanged();
    319             }
    320         }
    321     }
    322 
    323     private void updateState() {
    324         // Find all users with an active VPN
    325         SparseArray<VpnConfig> vpns = new SparseArray<>();
    326         try {
    327             for (UserInfo user : mUserManager.getUsers()) {
    328                 VpnConfig cfg = mConnectivityManagerService.getVpnConfig(user.id);
    329                 if (cfg == null) {
    330                     continue;
    331                 } else if (cfg.legacy) {
    332                     // Legacy VPNs should do nothing if the network is disconnected. Third-party
    333                     // VPN warnings need to continue as traffic can still go to the app.
    334                     LegacyVpnInfo legacyVpn = mConnectivityManagerService.getLegacyVpnInfo(user.id);
    335                     if (legacyVpn == null || legacyVpn.state != LegacyVpnInfo.STATE_CONNECTED) {
    336                         continue;
    337                     }
    338                 }
    339                 vpns.put(user.id, cfg);
    340             }
    341         } catch (RemoteException rme) {
    342             // Roll back to previous state
    343             Log.e(TAG, "Unable to list active VPNs", rme);
    344             return;
    345         }
    346         mCurrentVpns = vpns;
    347     }
    348 
    349     private String getPackageNameForVpnConfig(VpnConfig cfg) {
    350         if (cfg.legacy) {
    351             return null;
    352         }
    353         return cfg.user;
    354     }
    355 
    356     private boolean isVpnPackageBranded(String packageName) {
    357         boolean isBranded;
    358         try {
    359             ApplicationInfo info = mPackageManager.getApplicationInfo(packageName,
    360                 PackageManager.GET_META_DATA);
    361             if (info == null || info.metaData == null || !info.isSystemApp()) {
    362                 return false;
    363             }
    364             isBranded = info.metaData.getBoolean(VPN_BRANDED_META_DATA, false);
    365         } catch (NameNotFoundException e) {
    366             return false;
    367         }
    368         return isBranded;
    369     }
    370 
    371     private final NetworkCallback mNetworkCallback = new NetworkCallback() {
    372         @Override
    373         public void onAvailable(Network network) {
    374             if (DEBUG) Log.d(TAG, "onAvailable " + network.netId);
    375             updateState();
    376             fireCallbacks();
    377         };
    378 
    379         // TODO Find another way to receive VPN lost.  This may be delayed depending on
    380         // how long the VPN connection is held on to.
    381         @Override
    382         public void onLost(Network network) {
    383             if (DEBUG) Log.d(TAG, "onLost " + network.netId);
    384             updateState();
    385             fireCallbacks();
    386         };
    387     };
    388 
    389     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
    390         @Override public void onReceive(Context context, Intent intent) {
    391             if (KeyChain.ACTION_TRUST_STORE_CHANGED.equals(intent.getAction())) {
    392                 refreshCACerts();
    393             }
    394         }
    395     };
    396 
    397     protected class CACertLoader extends AsyncTask<Integer, Void, Pair<Integer, Boolean> > {
    398 
    399         @Override
    400         protected Pair<Integer, Boolean> doInBackground(Integer... userId) {
    401             try (KeyChainConnection conn = KeyChain.bindAsUser(mContext,
    402                                                                UserHandle.of(userId[0]))) {
    403                 boolean hasCACerts = !(conn.getService().getUserCaAliases().getList().isEmpty());
    404                 return new Pair<Integer, Boolean>(userId[0], hasCACerts);
    405             } catch (RemoteException | InterruptedException | AssertionError e) {
    406                 Log.i(TAG, e.getMessage());
    407                 new Handler(Dependency.get(Dependency.BG_LOOPER)).postDelayed(
    408                         () -> new CACertLoader().execute(userId[0]),
    409                         CA_CERT_LOADING_RETRY_TIME_IN_MS);
    410                 return new Pair<Integer, Boolean>(userId[0], null);
    411             }
    412         }
    413 
    414         @Override
    415         protected void onPostExecute(Pair<Integer, Boolean> result) {
    416             if (DEBUG) Log.d(TAG, "onPostExecute " + result);
    417             if (result.second != null) {
    418                 mHasCACerts.put(result.first, result.second);
    419                 fireCallbacks();
    420             }
    421         }
    422     }
    423 }
    424