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