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