/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.server.trust; import com.android.internal.content.PackageMonitor; import com.android.internal.widget.LockPatternUtils; import com.android.server.SystemService; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.Manifest; import android.app.ActivityManagerNative; import android.app.admin.DevicePolicyManager; import android.app.trust.ITrustListener; import android.app.trust.ITrustManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; import android.os.DeadObjectException; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.service.trust.TrustAgentService; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; import android.util.Slog; import android.util.SparseBooleanArray; import android.util.Xml; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; /** * Manages trust agents and trust listeners. * * It is responsible for binding to the enabled {@link android.service.trust.TrustAgentService}s * of each user and notifies them about events that are relevant to them. * It start and stops them based on the value of * {@link com.android.internal.widget.LockPatternUtils#getEnabledTrustAgents(int)}. * * It also keeps a set of {@link android.app.trust.ITrustListener}s that are notified whenever the * trust state changes for any user. * * Trust state and the setting of enabled agents is kept per user and each user has its own * instance of a {@link android.service.trust.TrustAgentService}. */ public class TrustManagerService extends SystemService { private static final boolean DEBUG = false; private static final String TAG = "TrustManagerService"; private static final Intent TRUST_AGENT_INTENT = new Intent(TrustAgentService.SERVICE_INTERFACE); private static final String PERMISSION_PROVIDE_AGENT = Manifest.permission.PROVIDE_TRUST_AGENT; private static final int MSG_REGISTER_LISTENER = 1; private static final int MSG_UNREGISTER_LISTENER = 2; private static final int MSG_DISPATCH_UNLOCK_ATTEMPT = 3; private static final int MSG_ENABLED_AGENTS_CHANGED = 4; private static final int MSG_REQUIRE_CREDENTIAL_ENTRY = 5; private final ArraySet mActiveAgents = new ArraySet(); private final ArrayList mTrustListeners = new ArrayList(); private final Receiver mReceiver = new Receiver(); private final SparseBooleanArray mUserHasAuthenticatedSinceBoot = new SparseBooleanArray(); /* package */ final TrustArchive mArchive = new TrustArchive(); private final Context mContext; private UserManager mUserManager; public TrustManagerService(Context context) { super(context); mContext = context; mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); } @Override public void onStart() { publishBinderService(Context.TRUST_SERVICE, mService); } @Override public void onBootPhase(int phase) { if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY && !isSafeMode()) { mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true); mReceiver.register(mContext); refreshAgentList(); } } // Agent management private static final class AgentInfo { CharSequence label; Drawable icon; ComponentName component; // service that implements ITrustAgent ComponentName settings; // setting to launch to modify agent. TrustAgentWrapper agent; int userId; @Override public boolean equals(Object other) { if (!(other instanceof AgentInfo)) { return false; } AgentInfo o = (AgentInfo) other; return component.equals(o.component) && userId == o.userId; } @Override public int hashCode() { return component.hashCode() * 31 + userId; } } private void updateTrustAll() { List userInfos = mUserManager.getUsers(true /* excludeDying */); for (UserInfo userInfo : userInfos) { updateTrust(userInfo.id, false); } } public void updateTrust(int userId, boolean initiatedByUser) { dispatchOnTrustManagedChanged(aggregateIsTrustManaged(userId), userId); dispatchOnTrustChanged(aggregateIsTrusted(userId), userId, initiatedByUser); } void refreshAgentList() { if (DEBUG) Slog.d(TAG, "refreshAgentList()"); PackageManager pm = mContext.getPackageManager(); List userInfos = mUserManager.getUsers(true /* excludeDying */); LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext); ArraySet obsoleteAgents = new ArraySet<>(); obsoleteAgents.addAll(mActiveAgents); for (UserInfo userInfo : userInfos) { if (lockPatternUtils.getKeyguardStoredPasswordQuality(userInfo.id) == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) continue; DevicePolicyManager dpm = lockPatternUtils.getDevicePolicyManager(); int disabledFeatures = dpm.getKeyguardDisabledFeatures(null, userInfo.id); final boolean disableTrustAgents = (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS) != 0; List enabledAgents = lockPatternUtils.getEnabledTrustAgents(userInfo.id); if (enabledAgents == null) { continue; } List resolveInfos = pm.queryIntentServicesAsUser(TRUST_AGENT_INTENT, PackageManager.GET_META_DATA, userInfo.id); for (ResolveInfo resolveInfo : resolveInfos) { if (resolveInfo.serviceInfo == null) continue; String packageName = resolveInfo.serviceInfo.packageName; if (pm.checkPermission(PERMISSION_PROVIDE_AGENT, packageName) != PackageManager.PERMISSION_GRANTED) { Log.w(TAG, "Skipping agent because package " + packageName + " does not have permission " + PERMISSION_PROVIDE_AGENT + "."); continue; } ComponentName name = getComponentName(resolveInfo); if (!enabledAgents.contains(name)) continue; if (disableTrustAgents) { List features = dpm.getTrustAgentFeaturesEnabled(null /* admin */, name); // Disable agent if no features are enabled. if (features == null || features.isEmpty()) continue; } AgentInfo agentInfo = new AgentInfo(); agentInfo.component = name; agentInfo.userId = userInfo.id; if (!mActiveAgents.contains(agentInfo)) { agentInfo.label = resolveInfo.loadLabel(pm); agentInfo.icon = resolveInfo.loadIcon(pm); agentInfo.settings = getSettingsComponentName(pm, resolveInfo); agentInfo.agent = new TrustAgentWrapper(mContext, this, new Intent().setComponent(name), userInfo.getUserHandle()); mActiveAgents.add(agentInfo); } else { obsoleteAgents.remove(agentInfo); } } } boolean trustMayHaveChanged = false; for (int i = 0; i < obsoleteAgents.size(); i++) { AgentInfo info = obsoleteAgents.valueAt(i); if (info.agent.isManagingTrust()) { trustMayHaveChanged = true; } info.agent.unbind(); mActiveAgents.remove(info); } if (trustMayHaveChanged) { updateTrustAll(); } } void updateDevicePolicyFeatures(int userId) { for (int i = 0; i < mActiveAgents.size(); i++) { AgentInfo info = mActiveAgents.valueAt(i); if (info.agent.isConnected()) { info.agent.updateDevicePolicyFeatures(); } } } private void removeAgentsOfPackage(String packageName) { boolean trustMayHaveChanged = false; for (int i = mActiveAgents.size() - 1; i >= 0; i--) { AgentInfo info = mActiveAgents.valueAt(i); if (packageName.equals(info.component.getPackageName())) { Log.i(TAG, "Resetting agent " + info.component.flattenToShortString()); if (info.agent.isManagingTrust()) { trustMayHaveChanged = true; } info.agent.unbind(); mActiveAgents.removeAt(i); } } if (trustMayHaveChanged) { updateTrustAll(); } } public void resetAgent(ComponentName name, int userId) { boolean trustMayHaveChanged = false; for (int i = mActiveAgents.size() - 1; i >= 0; i--) { AgentInfo info = mActiveAgents.valueAt(i); if (name.equals(info.component) && userId == info.userId) { Log.i(TAG, "Resetting agent " + info.component.flattenToShortString()); if (info.agent.isManagingTrust()) { trustMayHaveChanged = true; } info.agent.unbind(); mActiveAgents.removeAt(i); } } if (trustMayHaveChanged) { updateTrust(userId, false); } refreshAgentList(); } private ComponentName getSettingsComponentName(PackageManager pm, ResolveInfo resolveInfo) { if (resolveInfo == null || resolveInfo.serviceInfo == null || resolveInfo.serviceInfo.metaData == null) return null; String cn = null; XmlResourceParser parser = null; Exception caughtException = null; try { parser = resolveInfo.serviceInfo.loadXmlMetaData(pm, TrustAgentService.TRUST_AGENT_META_DATA); if (parser == null) { Slog.w(TAG, "Can't find " + TrustAgentService.TRUST_AGENT_META_DATA + " meta-data"); return null; } Resources res = pm.getResourcesForApplication(resolveInfo.serviceInfo.applicationInfo); AttributeSet attrs = Xml.asAttributeSet(parser); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { // Drain preamble. } String nodeName = parser.getName(); if (!"trust-agent".equals(nodeName)) { Slog.w(TAG, "Meta-data does not start with trust-agent tag"); return null; } TypedArray sa = res .obtainAttributes(attrs, com.android.internal.R.styleable.TrustAgent); cn = sa.getString(com.android.internal.R.styleable.TrustAgent_settingsActivity); sa.recycle(); } catch (PackageManager.NameNotFoundException e) { caughtException = e; } catch (IOException e) { caughtException = e; } catch (XmlPullParserException e) { caughtException = e; } finally { if (parser != null) parser.close(); } if (caughtException != null) { Slog.w(TAG, "Error parsing : " + resolveInfo.serviceInfo.packageName, caughtException); return null; } if (cn == null) { return null; } if (cn.indexOf('/') < 0) { cn = resolveInfo.serviceInfo.packageName + "/" + cn; } return ComponentName.unflattenFromString(cn); } private ComponentName getComponentName(ResolveInfo resolveInfo) { if (resolveInfo == null || resolveInfo.serviceInfo == null) return null; return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name); } // Agent dispatch and aggregation private boolean aggregateIsTrusted(int userId) { if (!mUserHasAuthenticatedSinceBoot.get(userId)) { return false; } for (int i = 0; i < mActiveAgents.size(); i++) { AgentInfo info = mActiveAgents.valueAt(i); if (info.userId == userId) { if (info.agent.isTrusted()) { return true; } } } return false; } private boolean aggregateIsTrustManaged(int userId) { if (!mUserHasAuthenticatedSinceBoot.get(userId)) { return false; } for (int i = 0; i < mActiveAgents.size(); i++) { AgentInfo info = mActiveAgents.valueAt(i); if (info.userId == userId) { if (info.agent.isManagingTrust()) { return true; } } } return false; } private void dispatchUnlockAttempt(boolean successful, int userId) { for (int i = 0; i < mActiveAgents.size(); i++) { AgentInfo info = mActiveAgents.valueAt(i); if (info.userId == userId) { info.agent.onUnlockAttempt(successful); } } if (successful) { updateUserHasAuthenticated(userId); } } private void updateUserHasAuthenticated(int userId) { if (!mUserHasAuthenticatedSinceBoot.get(userId)) { mUserHasAuthenticatedSinceBoot.put(userId, true); updateTrust(userId, false); } } private void requireCredentialEntry(int userId) { if (userId == UserHandle.USER_ALL) { mUserHasAuthenticatedSinceBoot.clear(); updateTrustAll(); } else { mUserHasAuthenticatedSinceBoot.put(userId, false); updateTrust(userId, false); } } // Listeners private void addListener(ITrustListener listener) { for (int i = 0; i < mTrustListeners.size(); i++) { if (mTrustListeners.get(i).asBinder() == listener.asBinder()) { return; } } mTrustListeners.add(listener); } private void removeListener(ITrustListener listener) { for (int i = 0; i < mTrustListeners.size(); i++) { if (mTrustListeners.get(i).asBinder() == listener.asBinder()) { mTrustListeners.remove(i); return; } } } private void dispatchOnTrustChanged(boolean enabled, int userId, boolean initiatedByUser) { if (!enabled) initiatedByUser = false; for (int i = 0; i < mTrustListeners.size(); i++) { try { mTrustListeners.get(i).onTrustChanged(enabled, userId, initiatedByUser); } catch (DeadObjectException e) { Slog.d(TAG, "Removing dead TrustListener."); mTrustListeners.remove(i); i--; } catch (RemoteException e) { Slog.e(TAG, "Exception while notifying TrustListener.", e); } } } private void dispatchOnTrustManagedChanged(boolean managed, int userId) { for (int i = 0; i < mTrustListeners.size(); i++) { try { mTrustListeners.get(i).onTrustManagedChanged(managed, userId); } catch (DeadObjectException e) { Slog.d(TAG, "Removing dead TrustListener."); mTrustListeners.remove(i); i--; } catch (RemoteException e) { Slog.e(TAG, "Exception while notifying TrustListener.", e); } } } // Plumbing private final IBinder mService = new ITrustManager.Stub() { @Override public void reportUnlockAttempt(boolean authenticated, int userId) throws RemoteException { enforceReportPermission(); mHandler.obtainMessage(MSG_DISPATCH_UNLOCK_ATTEMPT, authenticated ? 1 : 0, userId) .sendToTarget(); } @Override public void reportEnabledTrustAgentsChanged(int userId) throws RemoteException { enforceReportPermission(); // coalesce refresh messages. mHandler.removeMessages(MSG_ENABLED_AGENTS_CHANGED); mHandler.sendEmptyMessage(MSG_ENABLED_AGENTS_CHANGED); } @Override public void reportRequireCredentialEntry(int userId) throws RemoteException { enforceReportPermission(); if (userId == UserHandle.USER_ALL || userId >= UserHandle.USER_OWNER) { mHandler.obtainMessage(MSG_REQUIRE_CREDENTIAL_ENTRY, userId, 0).sendToTarget(); } else { throw new IllegalArgumentException( "userId must be an explicit user id or USER_ALL"); } } @Override public void registerTrustListener(ITrustListener trustListener) throws RemoteException { enforceListenerPermission(); mHandler.obtainMessage(MSG_REGISTER_LISTENER, trustListener).sendToTarget(); } @Override public void unregisterTrustListener(ITrustListener trustListener) throws RemoteException { enforceListenerPermission(); mHandler.obtainMessage(MSG_UNREGISTER_LISTENER, trustListener).sendToTarget(); } private void enforceReportPermission() { mContext.enforceCallingOrSelfPermission( Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE, "reporting trust events"); } private void enforceListenerPermission() { mContext.enforceCallingPermission(Manifest.permission.TRUST_LISTENER, "register trust listener"); } @Override protected void dump(FileDescriptor fd, final PrintWriter fout, String[] args) { mContext.enforceCallingPermission(Manifest.permission.DUMP, "dumping TrustManagerService"); final UserInfo currentUser; final List userInfos = mUserManager.getUsers(true /* excludeDying */); try { currentUser = ActivityManagerNative.getDefault().getCurrentUser(); } catch (RemoteException e) { throw new RuntimeException(e); } mHandler.runWithScissors(new Runnable() { @Override public void run() { fout.println("Trust manager state:"); for (UserInfo user : userInfos) { dumpUser(fout, user, user.id == currentUser.id); } } }, 1500); } private void dumpUser(PrintWriter fout, UserInfo user, boolean isCurrent) { fout.printf(" User \"%s\" (id=%d, flags=%#x)", user.name, user.id, user.flags); if (isCurrent) { fout.print(" (current)"); } fout.print(": trusted=" + dumpBool(aggregateIsTrusted(user.id))); fout.print(", trustManaged=" + dumpBool(aggregateIsTrustManaged(user.id))); fout.println(); fout.println(" Enabled agents:"); boolean duplicateSimpleNames = false; ArraySet simpleNames = new ArraySet(); for (AgentInfo info : mActiveAgents) { if (info.userId != user.id) { continue; } boolean trusted = info.agent.isTrusted(); fout.print(" "); fout.println(info.component.flattenToShortString()); fout.print(" bound=" + dumpBool(info.agent.isBound())); fout.print(", connected=" + dumpBool(info.agent.isConnected())); fout.print(", managingTrust=" + dumpBool(info.agent.isManagingTrust())); fout.print(", trusted=" + dumpBool(trusted)); fout.println(); if (trusted) { fout.println(" message=\"" + info.agent.getMessage() + "\""); } if (!info.agent.isConnected()) { String restartTime = TrustArchive.formatDuration( info.agent.getScheduledRestartUptimeMillis() - SystemClock.uptimeMillis()); fout.println(" restartScheduledAt=" + restartTime); } if (!simpleNames.add(TrustArchive.getSimpleName(info.component))) { duplicateSimpleNames = true; } } fout.println(" Events:"); mArchive.dump(fout, 50, user.id, " " /* linePrefix */, duplicateSimpleNames); fout.println(); } private String dumpBool(boolean b) { return b ? "1" : "0"; } }; private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_REGISTER_LISTENER: addListener((ITrustListener) msg.obj); break; case MSG_UNREGISTER_LISTENER: removeListener((ITrustListener) msg.obj); break; case MSG_DISPATCH_UNLOCK_ATTEMPT: dispatchUnlockAttempt(msg.arg1 != 0, msg.arg2); break; case MSG_ENABLED_AGENTS_CHANGED: refreshAgentList(); break; case MSG_REQUIRE_CREDENTIAL_ENTRY: requireCredentialEntry(msg.arg1); break; } } }; private final PackageMonitor mPackageMonitor = new PackageMonitor() { @Override public void onSomePackagesChanged() { refreshAgentList(); } @Override public boolean onPackageChanged(String packageName, int uid, String[] components) { // We're interested in all changes, even if just some components get enabled / disabled. return true; } @Override public void onPackageDisappeared(String packageName, int reason) { removeAgentsOfPackage(packageName); } }; private class Receiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals( intent.getAction())) { refreshAgentList(); updateDevicePolicyFeatures(getSendingUserId()); } else if (Intent.ACTION_USER_PRESENT.equals(intent.getAction())) { updateUserHasAuthenticated(getSendingUserId()); } } public void register(Context context) { IntentFilter filter = new IntentFilter(); filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); filter.addAction(Intent.ACTION_USER_PRESENT); context.registerReceiverAsUser(this, UserHandle.ALL, filter, null /* permission */, null /* scheduler */); } } }