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