/* * 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 android.annotation.TargetApi; import android.app.AlarmManager; import android.app.PendingIntent; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.PatternMatcher; import android.os.PersistableBundle; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.service.trust.ITrustAgentService; import android.service.trust.ITrustAgentServiceCallback; import android.service.trust.TrustAgentService; import android.util.Log; import android.util.Slog; import java.util.Collections; import java.util.List; /** * A wrapper around a TrustAgentService interface. Coordinates communication between * TrustManager and the actual TrustAgent. */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) public class TrustAgentWrapper { private static final String EXTRA_COMPONENT_NAME = "componentName"; private static final String TRUST_EXPIRED_ACTION = "android.server.trust.TRUST_EXPIRED_ACTION"; private static final String PERMISSION = android.Manifest.permission.PROVIDE_TRUST_AGENT; private static final boolean DEBUG = TrustManagerService.DEBUG; private static final String TAG = "TrustAgentWrapper"; private static final int MSG_GRANT_TRUST = 1; private static final int MSG_REVOKE_TRUST = 2; private static final int MSG_TRUST_TIMEOUT = 3; private static final int MSG_RESTART_TIMEOUT = 4; private static final int MSG_SET_TRUST_AGENT_FEATURES_COMPLETED = 5; private static final int MSG_MANAGING_TRUST = 6; private static final int MSG_ADD_ESCROW_TOKEN = 7; private static final int MSG_REMOVE_ESCROW_TOKEN = 8; private static final int MSG_ESCROW_TOKEN_STATE = 9; private static final int MSG_UNLOCK_USER = 10; /** * Time in uptime millis that we wait for the service connection, both when starting * and when the service disconnects. */ private static final long RESTART_TIMEOUT_MILLIS = 5 * 60000; /** * Long extra for {@link #MSG_GRANT_TRUST} */ private static final String DATA_DURATION = "duration"; private static final String DATA_ESCROW_TOKEN = "escrow_token"; private static final String DATA_HANDLE = "handle"; private static final String DATA_USER_ID = "user_id"; private final TrustManagerService mTrustManagerService; private final int mUserId; private final Context mContext; private final ComponentName mName; private ITrustAgentService mTrustAgentService; private boolean mBound; private long mScheduledRestartUptimeMillis; private long mMaximumTimeToLock; // from DevicePolicyManager private boolean mPendingSuccessfulUnlock = false; // Trust state private boolean mTrusted; private CharSequence mMessage; private boolean mTrustDisabledByDpm; private boolean mManagingTrust; private IBinder mSetTrustAgentFeaturesToken; private AlarmManager mAlarmManager; private final Intent mAlarmIntent; private PendingIntent mAlarmPendingIntent; private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { ComponentName component = intent.getParcelableExtra(EXTRA_COMPONENT_NAME); if (TRUST_EXPIRED_ACTION.equals(intent.getAction()) && mName.equals(component)) { mHandler.removeMessages(MSG_TRUST_TIMEOUT); mHandler.sendEmptyMessage(MSG_TRUST_TIMEOUT); } } }; private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_GRANT_TRUST: if (!isConnected()) { Log.w(TAG, "Agent is not connected, cannot grant trust: " + mName.flattenToShortString()); return; } mTrusted = true; mMessage = (CharSequence) msg.obj; int flags = msg.arg1; long durationMs = msg.getData().getLong(DATA_DURATION); if (durationMs > 0) { final long duration; if (mMaximumTimeToLock != 0) { // Enforce DevicePolicyManager timeout. This is here as a safeguard to // ensure trust agents are evaluating trust state at least as often as // the policy dictates. Admins that want more guarantees should be using // DevicePolicyManager#KEYGUARD_DISABLE_TRUST_AGENTS. duration = Math.min(durationMs, mMaximumTimeToLock); if (DEBUG) { Slog.d(TAG, "DPM lock timeout in effect. Timeout adjusted from " + durationMs + " to " + duration); } } else { duration = durationMs; } long expiration = SystemClock.elapsedRealtime() + duration; mAlarmPendingIntent = PendingIntent.getBroadcast(mContext, 0, mAlarmIntent, PendingIntent.FLAG_CANCEL_CURRENT); mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, expiration, mAlarmPendingIntent); } mTrustManagerService.mArchive.logGrantTrust(mUserId, mName, (mMessage != null ? mMessage.toString() : null), durationMs, flags); mTrustManagerService.updateTrust(mUserId, flags); break; case MSG_TRUST_TIMEOUT: if (DEBUG) Slog.d(TAG, "Trust timed out : " + mName.flattenToShortString()); mTrustManagerService.mArchive.logTrustTimeout(mUserId, mName); onTrustTimeout(); // Fall through. case MSG_REVOKE_TRUST: mTrusted = false; mMessage = null; mHandler.removeMessages(MSG_TRUST_TIMEOUT); if (msg.what == MSG_REVOKE_TRUST) { mTrustManagerService.mArchive.logRevokeTrust(mUserId, mName); } mTrustManagerService.updateTrust(mUserId, 0); break; case MSG_RESTART_TIMEOUT: Slog.w(TAG, "Connection attempt to agent " + mName.flattenToShortString() + " timed out, rebinding"); destroy(); mTrustManagerService.resetAgent(mName, mUserId); break; case MSG_SET_TRUST_AGENT_FEATURES_COMPLETED: IBinder token = (IBinder) msg.obj; boolean result = msg.arg1 != 0; if (mSetTrustAgentFeaturesToken == token) { mSetTrustAgentFeaturesToken = null; if (mTrustDisabledByDpm && result) { if (DEBUG) Slog.d(TAG, "Re-enabling agent because it acknowledged " + "enabled features: " + mName.flattenToShortString()); mTrustDisabledByDpm = false; mTrustManagerService.updateTrust(mUserId, 0); } } else { if (DEBUG) Slog.w(TAG, "Ignoring MSG_SET_TRUST_AGENT_FEATURES_COMPLETED " + "with obsolete token: " + mName.flattenToShortString()); } break; case MSG_MANAGING_TRUST: mManagingTrust = msg.arg1 != 0; if (!mManagingTrust) { mTrusted = false; mMessage = null; } mTrustManagerService.mArchive.logManagingTrust(mUserId, mName, mManagingTrust); mTrustManagerService.updateTrust(mUserId, 0); break; case MSG_ADD_ESCROW_TOKEN: { byte[] eToken = msg.getData().getByteArray(DATA_ESCROW_TOKEN); int userId = msg.getData().getInt(DATA_USER_ID); long handle = mTrustManagerService.addEscrowToken(eToken, userId); boolean resultDeliverred = false; try { if (mTrustAgentService != null) { mTrustAgentService.onEscrowTokenAdded( eToken, handle, UserHandle.of(userId)); resultDeliverred = true; } } catch (RemoteException e) { onError(e); } if (!resultDeliverred) { mTrustManagerService.removeEscrowToken(handle, userId); } break; } case MSG_ESCROW_TOKEN_STATE: { long handle = msg.getData().getLong(DATA_HANDLE); int userId = msg.getData().getInt(DATA_USER_ID); boolean active = mTrustManagerService.isEscrowTokenActive(handle, userId); try { if (mTrustAgentService != null) { mTrustAgentService.onTokenStateReceived(handle, active ? TrustAgentService.TOKEN_STATE_ACTIVE : TrustAgentService.TOKEN_STATE_INACTIVE); } } catch (RemoteException e) { onError(e); } break; } case MSG_REMOVE_ESCROW_TOKEN: { long handle = msg.getData().getLong(DATA_HANDLE); int userId = msg.getData().getInt(DATA_USER_ID); boolean success = mTrustManagerService.removeEscrowToken(handle, userId); try { if (mTrustAgentService != null) { mTrustAgentService.onEscrowTokenRemoved(handle, success); } } catch (RemoteException e) { onError(e); } break; } case MSG_UNLOCK_USER: { long handle = msg.getData().getLong(DATA_HANDLE); int userId = msg.getData().getInt(DATA_USER_ID); byte[] eToken = msg.getData().getByteArray(DATA_ESCROW_TOKEN); mTrustManagerService.unlockUserWithToken(handle, eToken, userId); break; } } } }; private ITrustAgentServiceCallback mCallback = new ITrustAgentServiceCallback.Stub() { @Override public void grantTrust(CharSequence userMessage, long durationMs, int flags) { if (DEBUG) Slog.d(TAG, "enableTrust(" + userMessage + ", durationMs = " + durationMs + ", flags = " + flags + ")"); Message msg = mHandler.obtainMessage( MSG_GRANT_TRUST, flags, 0, userMessage); msg.getData().putLong(DATA_DURATION, durationMs); msg.sendToTarget(); } @Override public void revokeTrust() { if (DEBUG) Slog.d(TAG, "revokeTrust()"); mHandler.sendEmptyMessage(MSG_REVOKE_TRUST); } @Override public void setManagingTrust(boolean managingTrust) { if (DEBUG) Slog.d(TAG, "managingTrust()"); mHandler.obtainMessage(MSG_MANAGING_TRUST, managingTrust ? 1 : 0, 0).sendToTarget(); } @Override public void onConfigureCompleted(boolean result, IBinder token) { if (DEBUG) Slog.d(TAG, "onSetTrustAgentFeaturesEnabledCompleted(result=" + result); mHandler.obtainMessage(MSG_SET_TRUST_AGENT_FEATURES_COMPLETED, result ? 1 : 0, 0, token).sendToTarget(); } @Override public void addEscrowToken(byte[] token, int userId) { if (mContext.getResources() .getBoolean(com.android.internal.R.bool.config_allowEscrowTokenForTrustAgent)) { throw new SecurityException("Escrow token API is not allowed."); } if (DEBUG) Slog.d(TAG, "adding escrow token for user " + userId); Message msg = mHandler.obtainMessage(MSG_ADD_ESCROW_TOKEN); msg.getData().putByteArray(DATA_ESCROW_TOKEN, token); msg.getData().putInt(DATA_USER_ID, userId); msg.sendToTarget(); } @Override public void isEscrowTokenActive(long handle, int userId) { if (mContext.getResources() .getBoolean(com.android.internal.R.bool.config_allowEscrowTokenForTrustAgent)) { throw new SecurityException("Escrow token API is not allowed."); } if (DEBUG) Slog.d(TAG, "checking the state of escrow token on user " + userId); Message msg = mHandler.obtainMessage(MSG_ESCROW_TOKEN_STATE); msg.getData().putLong(DATA_HANDLE, handle); msg.getData().putInt(DATA_USER_ID, userId); msg.sendToTarget(); } @Override public void removeEscrowToken(long handle, int userId) { if (mContext.getResources() .getBoolean(com.android.internal.R.bool.config_allowEscrowTokenForTrustAgent)) { throw new SecurityException("Escrow token API is not allowed."); } if (DEBUG) Slog.d(TAG, "removing escrow token on user " + userId); Message msg = mHandler.obtainMessage(MSG_REMOVE_ESCROW_TOKEN); msg.getData().putLong(DATA_HANDLE, handle); msg.getData().putInt(DATA_USER_ID, userId); msg.sendToTarget(); } @Override public void unlockUserWithToken(long handle, byte[] token, int userId) { if (mContext.getResources() .getBoolean(com.android.internal.R.bool.config_allowEscrowTokenForTrustAgent)) { throw new SecurityException("Escrow token API is not allowed."); } if (DEBUG) Slog.d(TAG, "unlocking user " + userId); Message msg = mHandler.obtainMessage(MSG_UNLOCK_USER); msg.getData().putInt(DATA_USER_ID, userId); msg.getData().putLong(DATA_HANDLE, handle); msg.getData().putByteArray(DATA_ESCROW_TOKEN, token); msg.sendToTarget(); } }; private final ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { if (DEBUG) Slog.d(TAG, "TrustAgent started : " + name.flattenToString()); mHandler.removeMessages(MSG_RESTART_TIMEOUT); mTrustAgentService = ITrustAgentService.Stub.asInterface(service); mTrustManagerService.mArchive.logAgentConnected(mUserId, name); setCallback(mCallback); updateDevicePolicyFeatures(); if (mPendingSuccessfulUnlock) { onUnlockAttempt(true); mPendingSuccessfulUnlock = false; } if (mTrustManagerService.isDeviceLockedInner(mUserId)) { onDeviceLocked(); } else { onDeviceUnlocked(); } } @Override public void onServiceDisconnected(ComponentName name) { if (DEBUG) Slog.d(TAG, "TrustAgent disconnected : " + name.flattenToShortString()); mTrustAgentService = null; mManagingTrust = false; mSetTrustAgentFeaturesToken = null; mTrustManagerService.mArchive.logAgentDied(mUserId, name); mHandler.sendEmptyMessage(MSG_REVOKE_TRUST); if (mBound) { scheduleRestart(); } // mTrustDisabledByDpm maintains state } }; public TrustAgentWrapper(Context context, TrustManagerService trustManagerService, Intent intent, UserHandle user) { mContext = context; mTrustManagerService = trustManagerService; mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); mUserId = user.getIdentifier(); mName = intent.getComponent(); mAlarmIntent = new Intent(TRUST_EXPIRED_ACTION).putExtra(EXTRA_COMPONENT_NAME, mName); mAlarmIntent.setData(Uri.parse(mAlarmIntent.toUri(Intent.URI_INTENT_SCHEME))); mAlarmIntent.setPackage(context.getPackageName()); final IntentFilter alarmFilter = new IntentFilter(TRUST_EXPIRED_ACTION); alarmFilter.addDataScheme(mAlarmIntent.getScheme()); final String pathUri = mAlarmIntent.toUri(Intent.URI_INTENT_SCHEME); alarmFilter.addDataPath(pathUri, PatternMatcher.PATTERN_LITERAL); // Schedules a restart for when connecting times out. If the connection succeeds, // the restart is canceled in mCallback's onConnected. scheduleRestart(); mBound = context.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, user); if (mBound) { mContext.registerReceiver(mBroadcastReceiver, alarmFilter, PERMISSION, null); } else { Log.e(TAG, "Can't bind to TrustAgent " + mName.flattenToShortString()); } } private void onError(Exception e) { Slog.w(TAG , "Exception ", e); } private void onTrustTimeout() { try { if (mTrustAgentService != null) mTrustAgentService.onTrustTimeout(); } catch (RemoteException e) { onError(e); } } /** * @see android.service.trust.TrustAgentService#onUnlockAttempt(boolean) */ public void onUnlockAttempt(boolean successful) { try { if (mTrustAgentService != null) { mTrustAgentService.onUnlockAttempt(successful); } else { mPendingSuccessfulUnlock = successful; } } catch (RemoteException e) { onError(e); } } /** * @see android.service.trust.TrustAgentService#onUnlockLockout(int) */ public void onUnlockLockout(int timeoutMs) { try { if (mTrustAgentService != null) { mTrustAgentService.onUnlockLockout(timeoutMs); } } catch (RemoteException e) { onError(e); } } /** * @see android.service.trust.TrustAgentService#onDeviceLocked() */ public void onDeviceLocked() { try { if (mTrustAgentService != null) mTrustAgentService.onDeviceLocked(); } catch (RemoteException e) { onError(e); } } /** * @see android.service.trust.TrustAgentService#onDeviceUnlocked() */ public void onDeviceUnlocked() { try { if (mTrustAgentService != null) mTrustAgentService.onDeviceUnlocked(); } catch (RemoteException e) { onError(e); } } private void setCallback(ITrustAgentServiceCallback callback) { try { if (mTrustAgentService != null) { mTrustAgentService.setCallback(callback); } } catch (RemoteException e) { onError(e); } } boolean updateDevicePolicyFeatures() { boolean trustDisabled = false; if (DEBUG) Slog.d(TAG, "updateDevicePolicyFeatures(" + mName + ")"); try { if (mTrustAgentService != null) { DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); if ((dpm.getKeyguardDisabledFeatures(null, mUserId) & DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS) != 0) { List config = dpm.getTrustAgentConfiguration( null, mName, mUserId); trustDisabled = true; if (DEBUG) Slog.d(TAG, "Detected trust agents disabled. Config = " + config); if (config != null && config.size() > 0) { if (DEBUG) { Slog.d(TAG, "TrustAgent " + mName.flattenToShortString() + " disabled until it acknowledges "+ config); } mSetTrustAgentFeaturesToken = new Binder(); mTrustAgentService.onConfigure(config, mSetTrustAgentFeaturesToken); } } else { mTrustAgentService.onConfigure(Collections.EMPTY_LIST, null); } final long maxTimeToLock = dpm.getMaximumTimeToLockForUserAndProfiles(mUserId); if (maxTimeToLock != mMaximumTimeToLock) { // If the timeout changes, cancel the alarm and send a timeout event to have // the agent re-evaluate trust. mMaximumTimeToLock = maxTimeToLock; if (mAlarmPendingIntent != null) { mAlarmManager.cancel(mAlarmPendingIntent); mAlarmPendingIntent = null; mHandler.sendEmptyMessage(MSG_TRUST_TIMEOUT); } } } } catch (RemoteException e) { onError(e); } if (mTrustDisabledByDpm != trustDisabled) { mTrustDisabledByDpm = trustDisabled; mTrustManagerService.updateTrust(mUserId, 0); } return trustDisabled; } public boolean isTrusted() { return mTrusted && mManagingTrust && !mTrustDisabledByDpm; } public boolean isManagingTrust() { return mManagingTrust && !mTrustDisabledByDpm; } public CharSequence getMessage() { return mMessage; } public void destroy() { mHandler.removeMessages(MSG_RESTART_TIMEOUT); if (!mBound) { return; } if (DEBUG) Slog.d(TAG, "TrustAgent unbound : " + mName.flattenToShortString()); mTrustManagerService.mArchive.logAgentStopped(mUserId, mName); mContext.unbindService(mConnection); mBound = false; mContext.unregisterReceiver(mBroadcastReceiver); mTrustAgentService = null; mSetTrustAgentFeaturesToken = null; mHandler.sendEmptyMessage(MSG_REVOKE_TRUST); } public boolean isConnected() { return mTrustAgentService != null; } public boolean isBound() { return mBound; } /** * If not connected, returns the time at which the agent is restarted. * * @return restart time in uptime millis. */ public long getScheduledRestartUptimeMillis() { return mScheduledRestartUptimeMillis; } private void scheduleRestart() { mHandler.removeMessages(MSG_RESTART_TIMEOUT); mScheduledRestartUptimeMillis = SystemClock.uptimeMillis() + RESTART_TIMEOUT_MILLIS; mHandler.sendEmptyMessageAtTime(MSG_RESTART_TIMEOUT, mScheduledRestartUptimeMillis); } }