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