/** * 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 android.service.trust; import android.Manifest; import android.annotation.IntDef; import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.app.Service; import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.PersistableBundle; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; import android.util.Slog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; /** * A service that notifies the system about whether it believes the environment of the device * to be trusted. * *

Trust agents may only be provided by the platform. It is expected that there is only * one trust agent installed on the platform. In the event there is more than one, * either trust agent can enable trust. *

* *

To extend this class, you must declare the service in your manifest file with * the {@link android.Manifest.permission#BIND_TRUST_AGENT} permission * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:

*
 * <service android:name=".TrustAgent"
 *          android:label="@string/service_name"
 *          android:permission="android.permission.BIND_TRUST_AGENT">
 *     <intent-filter>
 *         <action android:name="android.service.trust.TrustAgentService" />
 *     </intent-filter>
 *     <meta-data android:name="android.service.trust.trustagent"
 *          android:value="@xml/trust_agent" />
 * </service>
* *

The associated meta-data file can specify an activity that is accessible through Settings * and should allow configuring the trust agent, as defined in * {@link android.R.styleable#TrustAgent}. For example:

* *
 * <trust-agent xmlns:android="http://schemas.android.com/apk/res/android"
 *          android:settingsActivity=".TrustAgentSettings" />
* * @hide */ @SystemApi public class TrustAgentService extends Service { private final String TAG = TrustAgentService.class.getSimpleName() + "[" + getClass().getSimpleName() + "]"; private static final boolean DEBUG = false; /** * The {@link Intent} that must be declared as handled by the service. */ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) public static final String SERVICE_INTERFACE = "android.service.trust.TrustAgentService"; /** * The name of the {@code meta-data} tag pointing to additional configuration of the trust * agent. */ public static final String TRUST_AGENT_META_DATA = "android.service.trust.trustagent"; /** * Flag for {@link #grantTrust(CharSequence, long, int)} indicating that trust is being granted * as the direct result of user action - such as solving a security challenge. The hint is used * by the system to optimize the experience. Behavior may vary by device and release, so * one should only set this parameter if it meets the above criteria rather than relying on * the behavior of any particular device or release. */ public static final int FLAG_GRANT_TRUST_INITIATED_BY_USER = 1 << 0; /** * Flag for {@link #grantTrust(CharSequence, long, int)} indicating that the agent would like * to dismiss the keyguard. When using this flag, the {@code TrustAgentService} must ensure * it is only set in response to a direct user action with the expectation of dismissing the * keyguard. */ public static final int FLAG_GRANT_TRUST_DISMISS_KEYGUARD = 1 << 1; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, value = { FLAG_GRANT_TRUST_INITIATED_BY_USER, FLAG_GRANT_TRUST_DISMISS_KEYGUARD, }) public @interface GrantTrustFlags {} /** * Int enum indicating that escrow token is active. * See {@link #onEscrowTokenStateReceived(long, int)} * */ public static final int TOKEN_STATE_ACTIVE = 1; /** * Int enum indicating that escow token is inactive. * See {@link #onEscrowTokenStateReceived(long, int)} * */ public static final int TOKEN_STATE_INACTIVE = 0; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, value = { TOKEN_STATE_ACTIVE, TOKEN_STATE_INACTIVE, }) public @interface TokenState {} private static final int MSG_UNLOCK_ATTEMPT = 1; private static final int MSG_CONFIGURE = 2; private static final int MSG_TRUST_TIMEOUT = 3; private static final int MSG_DEVICE_LOCKED = 4; private static final int MSG_DEVICE_UNLOCKED = 5; private static final int MSG_UNLOCK_LOCKOUT = 6; private static final int MSG_ESCROW_TOKEN_ADDED = 7; private static final int MSG_ESCROW_TOKEN_STATE_RECEIVED = 8; private static final int MSG_ESCROW_TOKEN_REMOVED = 9; private static final String EXTRA_TOKEN = "token"; private static final String EXTRA_TOKEN_HANDLE = "token_handle"; private static final String EXTRA_USER_HANDLE = "user_handle"; private static final String EXTRA_TOKEN_STATE = "token_state"; private static final String EXTRA_TOKEN_REMOVED_RESULT = "token_removed_result"; /** * Class containing raw data for a given configuration request. */ private static final class ConfigurationData { final IBinder token; final List options; ConfigurationData(List opts, IBinder t) { options = opts; token = t; } } private ITrustAgentServiceCallback mCallback; private Runnable mPendingGrantTrustTask; private boolean mManagingTrust; // Lock used to access mPendingGrantTrustTask and mCallback. private final Object mLock = new Object(); private Handler mHandler = new Handler() { public void handleMessage(android.os.Message msg) { switch (msg.what) { case MSG_UNLOCK_ATTEMPT: onUnlockAttempt(msg.arg1 != 0); break; case MSG_UNLOCK_LOCKOUT: onDeviceUnlockLockout(msg.arg1); break; case MSG_CONFIGURE: { ConfigurationData data = (ConfigurationData) msg.obj; boolean result = onConfigure(data.options); if (data.token != null) { try { synchronized (mLock) { mCallback.onConfigureCompleted(result, data.token); } } catch (RemoteException e) { onError("calling onSetTrustAgentFeaturesEnabledCompleted()"); } } break; } case MSG_TRUST_TIMEOUT: onTrustTimeout(); break; case MSG_DEVICE_LOCKED: onDeviceLocked(); break; case MSG_DEVICE_UNLOCKED: onDeviceUnlocked(); break; case MSG_ESCROW_TOKEN_ADDED: { Bundle data = msg.getData(); byte[] token = data.getByteArray(EXTRA_TOKEN); long handle = data.getLong(EXTRA_TOKEN_HANDLE); UserHandle user = (UserHandle) data.getParcelable(EXTRA_USER_HANDLE); onEscrowTokenAdded(token, handle, user); break; } case MSG_ESCROW_TOKEN_STATE_RECEIVED: { Bundle data = msg.getData(); long handle = data.getLong(EXTRA_TOKEN_HANDLE); int tokenState = data.getInt(EXTRA_TOKEN_STATE, TOKEN_STATE_INACTIVE); onEscrowTokenStateReceived(handle, tokenState); break; } case MSG_ESCROW_TOKEN_REMOVED: { Bundle data = msg.getData(); long handle = data.getLong(EXTRA_TOKEN_HANDLE); boolean success = data.getBoolean(EXTRA_TOKEN_REMOVED_RESULT); onEscrowTokenRemoved(handle, success); break; } } } }; @Override public void onCreate() { super.onCreate(); ComponentName component = new ComponentName(this, getClass()); try { ServiceInfo serviceInfo = getPackageManager().getServiceInfo(component, 0 /* flags */); if (!Manifest.permission.BIND_TRUST_AGENT.equals(serviceInfo.permission)) { throw new IllegalStateException(component.flattenToShortString() + " is not declared with the permission " + "\"" + Manifest.permission.BIND_TRUST_AGENT + "\""); } } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Can't get ServiceInfo for " + component.toShortString()); } } /** * Called after the user attempts to authenticate in keyguard with their device credentials, * such as pin, pattern or password. * * @param successful true if the user successfully completed the challenge. */ public void onUnlockAttempt(boolean successful) { } /** * Called when the timeout provided by the agent expires. Note that this may be called earlier * than requested by the agent if the trust timeout is adjusted by the system or * {@link DevicePolicyManager}. The agent is expected to re-evaluate the trust state and only * call {@link #grantTrust(CharSequence, long, boolean)} if the trust state should be * continued. */ public void onTrustTimeout() { } /** * Called when the device enters a state where a PIN, pattern or * password must be entered to unlock it. */ public void onDeviceLocked() { } /** * Called when the device leaves a state where a PIN, pattern or * password must be entered to unlock it. */ public void onDeviceUnlocked() { } /** * Called when the device enters a temporary unlock lockout. * *

This occurs when the user has consecutively failed to unlock the device too many times, * and must wait until a timeout has passed to perform another attempt. The user may then only * use strong authentication mechanisms (PIN, pattern or password) to unlock the device. * Calls to {@link #grantTrust(CharSequence, long, int)} will be ignored until the user has * unlocked the device and {@link #onDeviceUnlocked()} is called. * * @param timeoutMs The amount of time, in milliseconds, that needs to elapse before the user * can attempt to unlock the device again. */ public void onDeviceUnlockLockout(long timeoutMs) { } /** * Called when an escrow token is added for user userId. * * @param token the added token * @param handle the handle to the corresponding internal synthetic password. A user is unlocked * by presenting both handle and escrow token. * @param user the user to which the escrow token is added. * */ public void onEscrowTokenAdded(byte[] token, long handle, UserHandle user) { } /** * Called when an escrow token state is received upon request. * * @param handle the handle to the internal synthetic password. * @param state the state of the requested escrow token, see {@link TokenState}. * */ public void onEscrowTokenStateReceived(long handle, @TokenState int tokenState) { } /** * Called when an escrow token is removed. * * @param handle the handle to the removed the synthetic password. * @param successful whether the removing operaiton is achieved. * */ public void onEscrowTokenRemoved(long handle, boolean successful) { } private void onError(String msg) { Slog.v(TAG, "Remote exception while " + msg); } /** * Called when device policy admin wants to enable specific options for agent in response to * {@link DevicePolicyManager#setKeyguardDisabledFeatures(ComponentName, int)} and * {@link DevicePolicyManager#setTrustAgentConfiguration(ComponentName, ComponentName, * PersistableBundle)}. *

Agents that support configuration options should overload this method and return 'true'. * * @param options The aggregated list of options or an empty list if no restrictions apply. * @return true if it supports configuration options. */ public boolean onConfigure(List options) { return false; } /** * Call to grant trust on the device. * * @param message describes why the device is trusted, e.g. "Trusted by location". * @param durationMs amount of time in milliseconds to keep the device in a trusted state. * Trust for this agent will automatically be revoked when the timeout expires unless * extended by a subsequent call to this function. The timeout is measured from the * invocation of this function as dictated by {@link SystemClock#elapsedRealtime())}. * For security reasons, the value should be no larger than necessary. * The value may be adjusted by the system as necessary to comply with a policy controlled * by the system or {@link DevicePolicyManager} restrictions. See {@link #onTrustTimeout()} * for determining when trust expires. * @param initiatedByUser this is a hint to the system that trust is being granted as the * direct result of user action - such as solving a security challenge. The hint is used * by the system to optimize the experience. Behavior may vary by device and release, so * one should only set this parameter if it meets the above criteria rather than relying on * the behavior of any particular device or release. Corresponds to * {@link #FLAG_GRANT_TRUST_INITIATED_BY_USER}. * @throws IllegalStateException if the agent is not currently managing trust. * * @deprecated use {@link #grantTrust(CharSequence, long, int)} instead. */ @Deprecated public final void grantTrust( final CharSequence message, final long durationMs, final boolean initiatedByUser) { grantTrust(message, durationMs, initiatedByUser ? FLAG_GRANT_TRUST_INITIATED_BY_USER : 0); } /** * Call to grant trust on the device. * * @param message describes why the device is trusted, e.g. "Trusted by location". * @param durationMs amount of time in milliseconds to keep the device in a trusted state. * Trust for this agent will automatically be revoked when the timeout expires unless * extended by a subsequent call to this function. The timeout is measured from the * invocation of this function as dictated by {@link SystemClock#elapsedRealtime())}. * For security reasons, the value should be no larger than necessary. * The value may be adjusted by the system as necessary to comply with a policy controlled * by the system or {@link DevicePolicyManager} restrictions. See {@link #onTrustTimeout()} * for determining when trust expires. * @param flags TBDocumented * @throws IllegalStateException if the agent is not currently managing trust. */ public final void grantTrust( final CharSequence message, final long durationMs, @GrantTrustFlags final int flags) { synchronized (mLock) { if (!mManagingTrust) { throw new IllegalStateException("Cannot grant trust if agent is not managing trust." + " Call setManagingTrust(true) first."); } if (mCallback != null) { try { mCallback.grantTrust(message.toString(), durationMs, flags); } catch (RemoteException e) { onError("calling enableTrust()"); } } else { // Remember trust has been granted so we can effectively grant it once the service // is bound. mPendingGrantTrustTask = new Runnable() { @Override public void run() { grantTrust(message, durationMs, flags); } }; } } } /** * Call to revoke trust on the device. */ public final void revokeTrust() { synchronized (mLock) { if (mPendingGrantTrustTask != null) { mPendingGrantTrustTask = null; } if (mCallback != null) { try { mCallback.revokeTrust(); } catch (RemoteException e) { onError("calling revokeTrust()"); } } } } /** * Call to notify the system if the agent is ready to manage trust. * * This property is not persistent across recreating the service and defaults to false. * Therefore this method is typically called when initializing the agent in {@link #onCreate}. * * @param managingTrust indicates if the agent would like to manage trust. */ public final void setManagingTrust(boolean managingTrust) { synchronized (mLock) { if (mManagingTrust != managingTrust) { mManagingTrust = managingTrust; if (mCallback != null) { try { mCallback.setManagingTrust(managingTrust); } catch (RemoteException e) { onError("calling setManagingTrust()"); } } } } } /** * Call to add an escrow token to derive a synthetic password. A synthetic password is an * alternaive to the user-set password/pin/pattern in order to unlock encrypted disk. An escrow * token can be taken and internally derive the synthetic password. The new added token will not * be acivated until the user input the correct PIN/Passcode/Password once. * * Result will be return by callback {@link #onEscrowTokenAdded(long, int)} * * @param token an escrow token of high entropy. * @param user the user which the escrow token will be added to. * */ public final void addEscrowToken(byte[] token, UserHandle user) { synchronized (mLock) { if (mCallback == null) { Slog.w(TAG, "Cannot add escrow token if the agent is not connecting to framework"); throw new IllegalStateException("Trust agent is not connected"); } try { mCallback.addEscrowToken(token, user.getIdentifier()); } catch (RemoteException e) { onError("calling addEscrowToken"); } } } /** * Call to check the active state of an escrow token. * * Result will be return in callback {@link #onEscrowTokenStateReceived(long, boolean)} * * @param handle the handle of escrow token to the internal synthetic password. * @param user the user which the escrow token is added to. * */ public final void isEscrowTokenActive(long handle, UserHandle user) { synchronized (mLock) { if (mCallback == null) { Slog.w(TAG, "Cannot add escrow token if the agent is not connecting to framework"); throw new IllegalStateException("Trust agent is not connected"); } try { mCallback.isEscrowTokenActive(handle, user.getIdentifier()); } catch (RemoteException e) { onError("calling isEscrowTokenActive"); } } } /** * Call to remove the escrow token. * * Result will be return in callback {@link #onEscrowTokenRemoved(long, boolean)} * * @param handle the handle of escrow tokent to the internal synthetic password. * @param user the user id which the escrow token is added to. * */ public final void removeEscrowToken(long handle, UserHandle user) { synchronized (mLock) { if (mCallback == null) { Slog.w(TAG, "Cannot add escrow token if the agent is not connecting to framework"); throw new IllegalStateException("Trust agent is not connected"); } try { mCallback.removeEscrowToken(handle, user.getIdentifier()); } catch (RemoteException e) { onError("callling removeEscrowToken"); } } } /** * Call to unlock user's FBE. * * @param handle the handle of escrow tokent to the internal synthetic password. * @param token the escrow token * @param user the user about to be unlocked. * */ public final void unlockUserWithToken(long handle, byte[] token, UserHandle user) { UserManager um = (UserManager) getSystemService(Context.USER_SERVICE); if (um.isUserUnlocked()) { Slog.i(TAG, "User already unlocked"); return; } synchronized (mLock) { if (mCallback == null) { Slog.w(TAG, "Cannot add escrow token if the agent is not connecting to framework"); throw new IllegalStateException("Trust agent is not connected"); } try { mCallback.unlockUserWithToken(handle, token, user.getIdentifier()); } catch (RemoteException e) { onError("calling unlockUserWithToken"); } } } @Override public final IBinder onBind(Intent intent) { if (DEBUG) Slog.v(TAG, "onBind() intent = " + intent); return new TrustAgentServiceWrapper(); } private final class TrustAgentServiceWrapper extends ITrustAgentService.Stub { @Override /* Binder API */ public void onUnlockAttempt(boolean successful) { mHandler.obtainMessage(MSG_UNLOCK_ATTEMPT, successful ? 1 : 0, 0).sendToTarget(); } @Override public void onUnlockLockout(int timeoutMs) { mHandler.obtainMessage(MSG_UNLOCK_LOCKOUT, timeoutMs, 0).sendToTarget(); } @Override /* Binder API */ public void onTrustTimeout() { mHandler.sendEmptyMessage(MSG_TRUST_TIMEOUT); } @Override /* Binder API */ public void onConfigure(List args, IBinder token) { mHandler.obtainMessage(MSG_CONFIGURE, new ConfigurationData(args, token)) .sendToTarget(); } @Override public void onDeviceLocked() throws RemoteException { mHandler.obtainMessage(MSG_DEVICE_LOCKED).sendToTarget(); } @Override public void onDeviceUnlocked() throws RemoteException { mHandler.obtainMessage(MSG_DEVICE_UNLOCKED).sendToTarget(); } @Override /* Binder API */ public void setCallback(ITrustAgentServiceCallback callback) { synchronized (mLock) { mCallback = callback; // The managingTrust property is false implicitly on the server-side, so we only // need to set it here if the agent has decided to manage trust. if (mManagingTrust) { try { mCallback.setManagingTrust(mManagingTrust); } catch (RemoteException e ) { onError("calling setManagingTrust()"); } } if (mPendingGrantTrustTask != null) { mPendingGrantTrustTask.run(); mPendingGrantTrustTask = null; } } } @Override public void onEscrowTokenAdded(byte[] token, long handle, UserHandle user) { Message msg = mHandler.obtainMessage(MSG_ESCROW_TOKEN_ADDED); msg.getData().putByteArray(EXTRA_TOKEN, token); msg.getData().putLong(EXTRA_TOKEN_HANDLE, handle); msg.getData().putParcelable(EXTRA_USER_HANDLE, user); msg.sendToTarget(); } public void onTokenStateReceived(long handle, int tokenState) { Message msg = mHandler.obtainMessage(MSG_ESCROW_TOKEN_STATE_RECEIVED); msg.getData().putLong(EXTRA_TOKEN_HANDLE, handle); msg.getData().putInt(EXTRA_TOKEN_STATE, tokenState); msg.sendToTarget(); } public void onEscrowTokenRemoved(long handle, boolean successful) { Message msg = mHandler.obtainMessage(MSG_ESCROW_TOKEN_REMOVED); msg.getData().putLong(EXTRA_TOKEN_HANDLE, handle); msg.getData().putBoolean(EXTRA_TOKEN_REMOVED_RESULT, successful); msg.sendToTarget(); } } }