blob: 4a9ccdee0522397f3fad64c1717e9ab7b7baeb5c [file] [log] [blame]
Kevin Chyn037c4d52018-06-11 19:17:32 -07001/*
2 * Copyright (C) 2018 The Android Open Source Project
Jim Millercb2ce6f2016-04-13 20:28:18 -07003 *
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
Kevin Chyn037c4d52018-06-11 19:17:32 -070014 * limitations under the License
Jim Millercb2ce6f2016-04-13 20:28:18 -070015 */
16
Kevin Chyn836f2cf2018-08-27 11:06:39 -070017package com.android.server.biometrics;
Jim Millercb2ce6f2016-04-13 20:28:18 -070018
Jim Millercb2ce6f2016-04-13 20:28:18 -070019import android.content.Context;
Kevin Chyna56dff72018-06-19 18:41:12 -070020import android.hardware.biometrics.BiometricAuthenticator;
Kevin Chyn037c4d52018-06-11 19:17:32 -070021import android.hardware.biometrics.BiometricConstants;
Kevin Chyn7782d142019-01-18 12:51:33 -080022import android.hardware.biometrics.BiometricsProtoEnums;
Jim Millercb2ce6f2016-04-13 20:28:18 -070023import android.os.IBinder;
24import android.os.RemoteException;
Kevin Chyn6cf54e82018-09-18 19:13:27 -070025import android.security.KeyStore;
Jim Millercb2ce6f2016-04-13 20:28:18 -070026import android.util.Slog;
27
Kevin Chyn6cf54e82018-09-18 19:13:27 -070028import java.util.ArrayList;
29
Jim Millercb2ce6f2016-04-13 20:28:18 -070030/**
31 * A class to keep track of the authentication state for a given client.
32 */
33public abstract class AuthenticationClient extends ClientMonitor {
34 private long mOpId;
35
Kevin Chyndf9d33e2017-05-03 21:40:12 -070036 public abstract int handleFailedAttempt();
Kevin Chyna38653c2019-02-11 17:46:21 -080037 public void resetFailedAttempts() {}
Kevin Chyne92cdae2018-11-21 16:35:04 -080038
Kevin Chyndf9d33e2017-05-03 21:40:12 -070039 public static final int LOCKOUT_NONE = 0;
40 public static final int LOCKOUT_TIMED = 1;
41 public static final int LOCKOUT_PERMANENT = 2;
42
Kevin Chyne92cdae2018-11-21 16:35:04 -080043 private final boolean mRequireConfirmation;
Kevin Chynaae4a152018-01-18 11:48:09 -080044
Kevin Chyna38653c2019-02-11 17:46:21 -080045 // We need to track this state since it's possible for applications to request for
46 // authentication while the device is already locked out. In that case, the client is created
47 // but not started yet. The user shouldn't receive the error haptics in this case.
48 private boolean mStarted;
Kevin Chyn9bc1d492019-06-21 15:31:50 -070049 private long mStartTimeMs;
Kevin Chyna38653c2019-02-11 17:46:21 -080050
Kevin Chynd4f43c22018-03-12 17:33:13 -070051 /**
52 * This method is called when authentication starts.
53 */
54 public abstract void onStart();
55
56 /**
Kevin Chyn5a2ff5d2018-08-29 19:07:30 -070057 * This method is called when a biometric is authenticated or authentication is stopped
Kevin Chynd4f43c22018-03-12 17:33:13 -070058 * (cancelled by the user, or an error such as lockout has occurred).
59 */
60 public abstract void onStop();
61
Kevin Chyna38653c2019-02-11 17:46:21 -080062 /**
63 * @return true if the framework should handle lockout.
64 */
65 public abstract boolean shouldFrameworkHandleLockout();
66
Kevin Chyn0ce70852019-05-10 10:29:18 -070067 public abstract boolean wasUserDetected();
68
Kevin Chyn4cc49f72019-04-24 13:53:35 -070069 public AuthenticationClient(Context context, Constants constants,
Kevin Chyn355c6bf2018-09-20 22:14:19 -070070 BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token,
71 BiometricServiceBase.ServiceListener listener, int targetUserId, int groupId, long opId,
Kevin Chyn87f257a2018-11-27 16:26:07 -080072 boolean restricted, String owner, int cookie, boolean requireConfirmation) {
Kevin Chyn4cc49f72019-04-24 13:53:35 -070073 super(context, constants, daemon, halDeviceId, token, listener, targetUserId, groupId,
Kevin Chyn87f257a2018-11-27 16:26:07 -080074 restricted, owner, cookie);
Jim Millercb2ce6f2016-04-13 20:28:18 -070075 mOpId = opId;
Kevin Chyn6cf54e82018-09-18 19:13:27 -070076 mRequireConfirmation = requireConfirmation;
Kevin Chynaae4a152018-01-18 11:48:09 -080077 }
78
Kevin Chyn9bc1d492019-06-21 15:31:50 -070079 protected long getStartTimeMs() {
80 return mStartTimeMs;
81 }
82
Kevin Chynaae4a152018-01-18 11:48:09 -080083 @Override
84 public void binderDied() {
85 super.binderDied();
86 // When the binder dies, we should stop the client. This probably belongs in
87 // ClientMonitor's binderDied(), but testing all the cases would be tricky.
88 // AuthenticationClient is the most user-visible case.
89 stop(false /* initiatedByClient */);
90 }
91
Kevin Chyn7782d142019-01-18 12:51:33 -080092 @Override
93 protected int statsAction() {
94 return BiometricsProtoEnums.ACTION_AUTHENTICATE;
95 }
96
Kevin Chyn3a0187192018-10-08 15:40:05 -070097 public boolean isBiometricPrompt() {
Kevin Chyn87f257a2018-11-27 16:26:07 -080098 return getCookie() != 0;
Kevin Chyn3a0187192018-10-08 15:40:05 -070099 }
100
Kevin Chyn87f257a2018-11-27 16:26:07 -0800101 public boolean getRequireConfirmation() {
Kevin Chyne92cdae2018-11-21 16:35:04 -0800102 return mRequireConfirmation;
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700103 }
104
Jim Millercb2ce6f2016-04-13 20:28:18 -0700105 @Override
Kevin Chyn7782d142019-01-18 12:51:33 -0800106 protected boolean isCryptoOperation() {
107 return mOpId != 0;
108 }
109
110 @Override
Kevin Chyna38653c2019-02-11 17:46:21 -0800111 public boolean onError(long deviceId, int error, int vendorCode) {
112 if (!shouldFrameworkHandleLockout()) {
113 switch (error) {
Kevin Chyne674e852019-04-24 12:39:40 -0700114 case BiometricConstants.BIOMETRIC_ERROR_TIMEOUT:
Kevin Chyn0ce70852019-05-10 10:29:18 -0700115 if (!wasUserDetected() && !isBiometricPrompt()) {
116 // No vibration if user was not detected on keyguard
117 break;
118 }
Kevin Chyna38653c2019-02-11 17:46:21 -0800119 case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT:
120 case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
121 if (mStarted) {
122 vibrateError();
123 }
124 break;
125 default:
126 break;
127 }
128 }
129 return super.onError(deviceId, error, vendorCode);
130 }
131
132 @Override
Kevin Chynb528d692018-07-20 11:53:14 -0700133 public boolean onAuthenticated(BiometricAuthenticator.Identifier identifier,
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700134 boolean authenticated, ArrayList<Byte> token) {
Kevin Chyn4858da42019-04-11 13:02:56 -0700135 super.logOnAuthenticated(getContext(), authenticated, mRequireConfirmation,
136 getTargetUserId(), isBiometricPrompt());
Kevin Chyn7782d142019-01-18 12:51:33 -0800137
Kevin Chyne92cdae2018-11-21 16:35:04 -0800138 final BiometricServiceBase.ServiceListener listener = getListener();
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700139
Kevin Chyn4cc49f72019-04-24 13:53:35 -0700140 mMetricsLogger.action(mConstants.actionBiometricAuth(), authenticated);
Jim Millercb2ce6f2016-04-13 20:28:18 -0700141 boolean result = false;
Jim Millercb2ce6f2016-04-13 20:28:18 -0700142
Kevin Chyne92cdae2018-11-21 16:35:04 -0800143 try {
Kevin Chynd4ee8af2019-04-09 12:10:11 -0700144 if (DEBUG) Slog.v(getLogTag(), "onAuthenticated(" + authenticated + ")"
145 + ", ID:" + identifier.getBiometricId()
146 + ", Owner: " + getOwnerString()
147 + ", isBP: " + isBiometricPrompt()
148 + ", listener: " + listener
Kevin Chyn8d2694a2019-04-11 18:30:40 -0700149 + ", requireConfirmation: " + mRequireConfirmation
150 + ", user: " + getTargetUserId());
Kevin Chynd4ee8af2019-04-09 12:10:11 -0700151
Kevin Chyne92cdae2018-11-21 16:35:04 -0800152 if (authenticated) {
153 mAlreadyDone = true;
Kevin Chynd4ee8af2019-04-09 12:10:11 -0700154
Kevin Chyne92cdae2018-11-21 16:35:04 -0800155 if (listener != null) {
156 vibrateSuccess();
Kevin Chynaae4a152018-01-18 11:48:09 -0800157 }
Kevin Chyne92cdae2018-11-21 16:35:04 -0800158 result = true;
Kevin Chyna38653c2019-02-11 17:46:21 -0800159 if (shouldFrameworkHandleLockout()) {
160 resetFailedAttempts();
161 }
Kevin Chyne92cdae2018-11-21 16:35:04 -0800162 onStop();
Kevin Chynaae4a152018-01-18 11:48:09 -0800163
Kevin Chyne92cdae2018-11-21 16:35:04 -0800164 final byte[] byteToken = new byte[token.size()];
165 for (int i = 0; i < token.size(); i++) {
166 byteToken[i] = token.get(i);
167 }
168 if (isBiometricPrompt() && listener != null) {
169 // BiometricService will add the token to keystore
170 listener.onAuthenticationSucceededInternal(mRequireConfirmation, byteToken);
171 } else if (!isBiometricPrompt() && listener != null) {
172 KeyStore.getInstance().addAuthToken(byteToken);
173 try {
174 // Explicitly have if/else here to make it super obvious in case the code is
175 // touched in the future.
176 if (!getIsRestricted()) {
177 listener.onAuthenticationSucceeded(
178 getHalDeviceId(), identifier, getTargetUserId());
179 } else {
180 listener.onAuthenticationSucceeded(
181 getHalDeviceId(), null, getTargetUserId());
182 }
183 } catch (RemoteException e) {
184 Slog.e(getLogTag(), "Remote exception", e);
185 }
186 } else {
187 // Client not listening
188 Slog.w(getLogTag(), "Client not listening");
189 result = true;
190 }
191 } else {
192 if (listener != null) {
Kevin Chyne92cdae2018-11-21 16:35:04 -0800193 vibrateError();
Jim Millercb2ce6f2016-04-13 20:28:18 -0700194 }
Kevin Chyna38653c2019-02-11 17:46:21 -0800195
Kevin Chyne92cdae2018-11-21 16:35:04 -0800196 // Allow system-defined limit of number of attempts before giving up
197 final int lockoutMode = handleFailedAttempt();
Kevin Chyna38653c2019-02-11 17:46:21 -0800198 if (lockoutMode != LOCKOUT_NONE && shouldFrameworkHandleLockout()) {
Kevin Chyne92cdae2018-11-21 16:35:04 -0800199 Slog.w(getLogTag(), "Forcing lockout (driver code should do this!), mode("
200 + lockoutMode + ")");
201 stop(false);
202 final int errorCode = lockoutMode == LOCKOUT_TIMED
203 ? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
204 : BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
Kevin Chyn7782d142019-01-18 12:51:33 -0800205 onError(getHalDeviceId(), errorCode, 0 /* vendorCode */);
Kevin Chyn23289ef2018-11-28 16:32:36 -0800206 } else {
207 // Don't send onAuthenticationFailed if we're in lockout, it causes a
208 // janky UI on Keyguard/BiometricPrompt since "authentication failed"
209 // will show briefly and be replaced by "device locked out" message.
210 if (listener != null) {
211 if (isBiometricPrompt()) {
212 listener.onAuthenticationFailedInternal(getCookie(),
213 getRequireConfirmation());
214 } else {
215 listener.onAuthenticationFailed(getHalDeviceId());
216 }
217 }
Jim Millerd1974862016-05-03 18:35:18 -0700218 }
Kevin Chyna38653c2019-02-11 17:46:21 -0800219 result = lockoutMode != LOCKOUT_NONE; // in a lockout mode
Jim Millerd1974862016-05-03 18:35:18 -0700220 }
Kevin Chyne92cdae2018-11-21 16:35:04 -0800221 } catch (RemoteException e) {
222 Slog.e(getLogTag(), "Remote exception", e);
223 result = true;
Jim Millercb2ce6f2016-04-13 20:28:18 -0700224 }
225 return result;
226 }
227
228 /**
229 * Start authentication
230 */
231 @Override
232 public int start() {
Kevin Chyna38653c2019-02-11 17:46:21 -0800233 mStarted = true;
Kevin Chynd4f43c22018-03-12 17:33:13 -0700234 onStart();
Jim Millercb2ce6f2016-04-13 20:28:18 -0700235 try {
Kevin Chyn9bc1d492019-06-21 15:31:50 -0700236 mStartTimeMs = System.currentTimeMillis();
Kevin Chyn037c4d52018-06-11 19:17:32 -0700237 final int result = getDaemonWrapper().authenticate(mOpId, getGroupId());
Jim Millercb2ce6f2016-04-13 20:28:18 -0700238 if (result != 0) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700239 Slog.w(getLogTag(), "startAuthentication failed, result=" + result);
Kevin Chyn4cc49f72019-04-24 13:53:35 -0700240 mMetricsLogger.histogram(mConstants.tagAuthStartError(), result);
Kevin Chyna56dff72018-06-19 18:41:12 -0700241 onError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
242 0 /* vendorCode */);
Jim Millercb2ce6f2016-04-13 20:28:18 -0700243 return result;
244 }
Kevin Chyn037c4d52018-06-11 19:17:32 -0700245 if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() + " is authenticating...");
Jim Millercb2ce6f2016-04-13 20:28:18 -0700246 } catch (RemoteException e) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700247 Slog.e(getLogTag(), "startAuthentication failed", e);
Jim Millercb2ce6f2016-04-13 20:28:18 -0700248 return ERROR_ESRCH;
249 }
250 return 0; // success
251 }
252
253 @Override
254 public int stop(boolean initiatedByClient) {
Kevin Chyn625a0142017-04-10 14:53:59 -0700255 if (mAlreadyCancelled) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700256 Slog.w(getLogTag(), "stopAuthentication: already cancelled!");
Kevin Chyn625a0142017-04-10 14:53:59 -0700257 return 0;
258 }
Kevin Chynaae4a152018-01-18 11:48:09 -0800259
Kevin Chyna38653c2019-02-11 17:46:21 -0800260 mStarted = false;
261
Kevin Chynd4f43c22018-03-12 17:33:13 -0700262 onStop();
Kevin Chyn037c4d52018-06-11 19:17:32 -0700263
Jim Millercb2ce6f2016-04-13 20:28:18 -0700264 try {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700265 final int result = getDaemonWrapper().cancel();
Jim Millercb2ce6f2016-04-13 20:28:18 -0700266 if (result != 0) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700267 Slog.w(getLogTag(), "stopAuthentication failed, result=" + result);
Jim Millercb2ce6f2016-04-13 20:28:18 -0700268 return result;
269 }
Kevin Chyn5a2ff5d2018-08-29 19:07:30 -0700270 if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() +
271 " is no longer authenticating");
Jim Millercb2ce6f2016-04-13 20:28:18 -0700272 } catch (RemoteException e) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700273 Slog.e(getLogTag(), "stopAuthentication failed", e);
Jim Millercb2ce6f2016-04-13 20:28:18 -0700274 return ERROR_ESRCH;
275 }
Kevin Chyn037c4d52018-06-11 19:17:32 -0700276
Kevin Chyn625a0142017-04-10 14:53:59 -0700277 mAlreadyCancelled = true;
Jim Millercb2ce6f2016-04-13 20:28:18 -0700278 return 0; // success
279 }
280
281 @Override
Kevin Chyna56dff72018-06-19 18:41:12 -0700282 public boolean onEnrollResult(BiometricAuthenticator.Identifier identifier,
283 int remaining) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700284 if (DEBUG) Slog.w(getLogTag(), "onEnrollResult() called for authenticate!");
Jim Millercb2ce6f2016-04-13 20:28:18 -0700285 return true; // Invalid for Authenticate
286 }
287
288 @Override
Kevin Chyna56dff72018-06-19 18:41:12 -0700289 public boolean onRemoved(BiometricAuthenticator.Identifier identifier, int remaining) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700290 if (DEBUG) Slog.w(getLogTag(), "onRemoved() called for authenticate!");
Jim Millercb2ce6f2016-04-13 20:28:18 -0700291 return true; // Invalid for Authenticate
292 }
293
294 @Override
Kevin Chyna56dff72018-06-19 18:41:12 -0700295 public boolean onEnumerationResult(BiometricAuthenticator.Identifier identifier,
296 int remaining) {
Kevin Chyn037c4d52018-06-11 19:17:32 -0700297 if (DEBUG) Slog.w(getLogTag(), "onEnumerationResult() called for authenticate!");
Jim Millercb2ce6f2016-04-13 20:28:18 -0700298 return true; // Invalid for Authenticate
299 }
300}