1 /*
2  * Copyright (C) 2018 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  */
16 
17 package com.android.server.biometrics;
18 
19 import android.content.Context;
20 import android.hardware.biometrics.BiometricAuthenticator;
21 import android.hardware.biometrics.BiometricConstants;
22 import android.hardware.biometrics.BiometricsProtoEnums;
23 import android.os.IBinder;
24 import android.os.RemoteException;
25 import android.security.KeyStore;
26 import android.util.Slog;
27 
28 import java.util.ArrayList;
29 
30 /**
31  * A class to keep track of the authentication state for a given client.
32  */
33 public abstract class AuthenticationClient extends ClientMonitor {
34     private long mOpId;
35 
handleFailedAttempt()36     public abstract int handleFailedAttempt();
resetFailedAttempts()37     public void resetFailedAttempts() {}
38 
39     public static final int LOCKOUT_NONE = 0;
40     public static final int LOCKOUT_TIMED = 1;
41     public static final int LOCKOUT_PERMANENT = 2;
42 
43     private final boolean mRequireConfirmation;
44 
45     // 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;
49     private long mStartTimeMs;
50 
51     /**
52      * This method is called when authentication starts.
53      */
onStart()54     public abstract void onStart();
55 
56     /**
57      * This method is called when a biometric is authenticated or authentication is stopped
58      * (cancelled by the user, or an error such as lockout has occurred).
59      */
onStop()60     public abstract void onStop();
61 
62     /**
63      * @return true if the framework should handle lockout.
64      */
shouldFrameworkHandleLockout()65     public abstract boolean shouldFrameworkHandleLockout();
66 
wasUserDetected()67     public abstract boolean wasUserDetected();
68 
isStrongBiometric()69     public abstract boolean isStrongBiometric();
70 
AuthenticationClient(Context context, Constants constants, BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token, BiometricServiceBase.ServiceListener listener, int targetUserId, int groupId, long opId, boolean restricted, String owner, int cookie, boolean requireConfirmation)71     public AuthenticationClient(Context context, Constants constants,
72             BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token,
73             BiometricServiceBase.ServiceListener listener, int targetUserId, int groupId, long opId,
74             boolean restricted, String owner, int cookie, boolean requireConfirmation) {
75         super(context, constants, daemon, halDeviceId, token, listener, targetUserId, groupId,
76                 restricted, owner, cookie);
77         mOpId = opId;
78         mRequireConfirmation = requireConfirmation;
79     }
80 
getStartTimeMs()81     protected long getStartTimeMs() {
82         return mStartTimeMs;
83     }
84 
85     @Override
binderDied()86     public void binderDied() {
87         final boolean clearListener = !isBiometricPrompt();
88         binderDiedInternal(clearListener);
89     }
90 
91     @Override
statsAction()92     protected int statsAction() {
93         return BiometricsProtoEnums.ACTION_AUTHENTICATE;
94     }
95 
isBiometricPrompt()96     public boolean isBiometricPrompt() {
97         return getCookie() != 0;
98     }
99 
getRequireConfirmation()100     public boolean getRequireConfirmation() {
101         return mRequireConfirmation;
102     }
103 
104     @Override
isCryptoOperation()105     protected boolean isCryptoOperation() {
106         return mOpId != 0;
107     }
108 
109     @Override
onError(long deviceId, int error, int vendorCode)110     public boolean onError(long deviceId, int error, int vendorCode) {
111         if (!shouldFrameworkHandleLockout()) {
112             switch (error) {
113                 case BiometricConstants.BIOMETRIC_ERROR_TIMEOUT:
114                     if (!wasUserDetected() && !isBiometricPrompt()) {
115                         // No vibration if user was not detected on keyguard
116                         break;
117                     }
118                 case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT:
119                 case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
120                     if (mStarted) {
121                         vibrateError();
122                     }
123                     break;
124                 default:
125                     break;
126             }
127         }
128         return super.onError(deviceId, error, vendorCode);
129     }
130 
131     @Override
onAuthenticated(BiometricAuthenticator.Identifier identifier, boolean authenticated, ArrayList<Byte> token)132     public boolean onAuthenticated(BiometricAuthenticator.Identifier identifier,
133             boolean authenticated, ArrayList<Byte> token) {
134         super.logOnAuthenticated(getContext(), authenticated, mRequireConfirmation,
135                 getTargetUserId(), isBiometricPrompt());
136 
137         final BiometricServiceBase.ServiceListener listener = getListener();
138 
139         mMetricsLogger.action(mConstants.actionBiometricAuth(), authenticated);
140         boolean result = false;
141 
142         try {
143             if (DEBUG) Slog.v(getLogTag(), "onAuthenticated(" + authenticated + ")"
144                     + ", ID:" + identifier.getBiometricId()
145                     + ", Owner: " + getOwnerString()
146                     + ", isBP: " + isBiometricPrompt()
147                     + ", listener: " + listener
148                     + ", requireConfirmation: " + mRequireConfirmation
149                     + ", user: " + getTargetUserId());
150 
151             if (authenticated) {
152                 mAlreadyDone = true;
153 
154                 if (listener != null) {
155                     vibrateSuccess();
156                 }
157                 result = true;
158                 if (shouldFrameworkHandleLockout()) {
159                     resetFailedAttempts();
160                 }
161                 onStop();
162 
163                 final byte[] byteToken = new byte[token.size()];
164                 for (int i = 0; i < token.size(); i++) {
165                     byteToken[i] = token.get(i);
166                 }
167                 if (isBiometricPrompt() && listener != null) {
168                     // BiometricService will add the token to keystore
169                     listener.onAuthenticationSucceededInternal(mRequireConfirmation, byteToken,
170                             isStrongBiometric());
171                 } else if (!isBiometricPrompt() && listener != null) {
172                     if (isStrongBiometric()) {
173                         KeyStore.getInstance().addAuthToken(byteToken);
174                     } else {
175                         Slog.d(getLogTag(), "Skipping addAuthToken");
176                     }
177 
178                     try {
179                         // Explicitly have if/else here to make it super obvious in case the code is
180                         // touched in the future.
181                         if (!getIsRestricted()) {
182                             listener.onAuthenticationSucceeded(
183                                     getHalDeviceId(), identifier, getTargetUserId());
184                         } else {
185                             listener.onAuthenticationSucceeded(
186                                     getHalDeviceId(), null, getTargetUserId());
187                         }
188                     } catch (RemoteException e) {
189                         Slog.e(getLogTag(), "Remote exception", e);
190                     }
191                 } else {
192                     // Client not listening
193                     Slog.w(getLogTag(), "Client not listening");
194                     result = true;
195                 }
196             } else {
197                 if (listener != null) {
198                     vibrateError();
199                 }
200 
201                 // Allow system-defined limit of number of attempts before giving up
202                 final int lockoutMode = handleFailedAttempt();
203                 if (lockoutMode != LOCKOUT_NONE && shouldFrameworkHandleLockout()) {
204                     Slog.w(getLogTag(), "Forcing lockout (driver code should do this!), mode("
205                             + lockoutMode + ")");
206                     stop(false);
207                     final int errorCode = lockoutMode == LOCKOUT_TIMED
208                             ? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
209                             : BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
210                     onError(getHalDeviceId(), errorCode, 0 /* vendorCode */);
211                 } else {
212                     // Don't send onAuthenticationFailed if we're in lockout, it causes a
213                     // janky UI on Keyguard/BiometricPrompt since "authentication failed"
214                     // will show briefly and be replaced by "device locked out" message.
215                     if (listener != null) {
216                         if (isBiometricPrompt()) {
217                             listener.onAuthenticationFailedInternal();
218                         } else {
219                             listener.onAuthenticationFailed(getHalDeviceId());
220                         }
221                     }
222                 }
223                 result = lockoutMode != LOCKOUT_NONE; // in a lockout mode
224             }
225         } catch (RemoteException e) {
226             Slog.e(getLogTag(), "Remote exception", e);
227             result = true;
228         }
229         return result;
230     }
231 
232     /**
233      * Start authentication
234      */
235     @Override
start()236     public int start() {
237         mStarted = true;
238         onStart();
239         try {
240             mStartTimeMs = System.currentTimeMillis();
241             final int result = getDaemonWrapper().authenticate(mOpId, getGroupId());
242             if (result != 0) {
243                 Slog.w(getLogTag(), "startAuthentication failed, result=" + result);
244                 mMetricsLogger.histogram(mConstants.tagAuthStartError(), result);
245                 onError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
246                         0 /* vendorCode */);
247                 return result;
248             }
249             if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() + " is authenticating...");
250         } catch (RemoteException e) {
251             Slog.e(getLogTag(), "startAuthentication failed", e);
252             return ERROR_ESRCH;
253         }
254         return 0; // success
255     }
256 
257     @Override
stop(boolean initiatedByClient)258     public int stop(boolean initiatedByClient) {
259         if (mAlreadyCancelled) {
260             Slog.w(getLogTag(), "stopAuthentication: already cancelled!");
261             return 0;
262         }
263 
264         mStarted = false;
265 
266         onStop();
267 
268         try {
269             final int result = getDaemonWrapper().cancel();
270             if (result != 0) {
271                 Slog.w(getLogTag(), "stopAuthentication failed, result=" + result);
272                 return result;
273             }
274             if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() +
275                     " is no longer authenticating");
276         } catch (RemoteException e) {
277             Slog.e(getLogTag(), "stopAuthentication failed", e);
278             return ERROR_ESRCH;
279         }
280 
281         mAlreadyCancelled = true;
282         return 0; // success
283     }
284 
285     @Override
onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining)286     public boolean onEnrollResult(BiometricAuthenticator.Identifier identifier,
287             int remaining) {
288         if (DEBUG) Slog.w(getLogTag(), "onEnrollResult() called for authenticate!");
289         return true; // Invalid for Authenticate
290     }
291 
292     @Override
onRemoved(BiometricAuthenticator.Identifier identifier, int remaining)293     public boolean onRemoved(BiometricAuthenticator.Identifier identifier, int remaining) {
294         if (DEBUG) Slog.w(getLogTag(), "onRemoved() called for authenticate!");
295         return true; // Invalid for Authenticate
296     }
297 
298     @Override
onEnumerationResult(BiometricAuthenticator.Identifier identifier, int remaining)299     public boolean onEnumerationResult(BiometricAuthenticator.Identifier identifier,
300             int remaining) {
301         if (DEBUG) Slog.w(getLogTag(), "onEnumerationResult() called for authenticate!");
302         return true; // Invalid for Authenticate
303     }
304 }
305