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 
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)69     public AuthenticationClient(Context context, Constants constants,
70             BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token,
71             BiometricServiceBase.ServiceListener listener, int targetUserId, int groupId, long opId,
72             boolean restricted, String owner, int cookie, boolean requireConfirmation) {
73         super(context, constants, daemon, halDeviceId, token, listener, targetUserId, groupId,
74                 restricted, owner, cookie);
75         mOpId = opId;
76         mRequireConfirmation = requireConfirmation;
77     }
78 
getStartTimeMs()79     protected long getStartTimeMs() {
80         return mStartTimeMs;
81     }
82 
83     @Override
binderDied()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 
92     @Override
statsAction()93     protected int statsAction() {
94         return BiometricsProtoEnums.ACTION_AUTHENTICATE;
95     }
96 
isBiometricPrompt()97     public boolean isBiometricPrompt() {
98         return getCookie() != 0;
99     }
100 
getRequireConfirmation()101     public boolean getRequireConfirmation() {
102         return mRequireConfirmation;
103     }
104 
105     @Override
isCryptoOperation()106     protected boolean isCryptoOperation() {
107         return mOpId != 0;
108     }
109 
110     @Override
onError(long deviceId, int error, int vendorCode)111     public boolean onError(long deviceId, int error, int vendorCode) {
112         if (!shouldFrameworkHandleLockout()) {
113             switch (error) {
114                 case BiometricConstants.BIOMETRIC_ERROR_TIMEOUT:
115                     if (!wasUserDetected() && !isBiometricPrompt()) {
116                         // No vibration if user was not detected on keyguard
117                         break;
118                     }
119                 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
onAuthenticated(BiometricAuthenticator.Identifier identifier, boolean authenticated, ArrayList<Byte> token)133     public boolean onAuthenticated(BiometricAuthenticator.Identifier identifier,
134             boolean authenticated, ArrayList<Byte> token) {
135         super.logOnAuthenticated(getContext(), authenticated, mRequireConfirmation,
136                 getTargetUserId(), isBiometricPrompt());
137 
138         final BiometricServiceBase.ServiceListener listener = getListener();
139 
140         mMetricsLogger.action(mConstants.actionBiometricAuth(), authenticated);
141         boolean result = false;
142 
143         try {
144             if (DEBUG) Slog.v(getLogTag(), "onAuthenticated(" + authenticated + ")"
145                     + ", ID:" + identifier.getBiometricId()
146                     + ", Owner: " + getOwnerString()
147                     + ", isBP: " + isBiometricPrompt()
148                     + ", listener: " + listener
149                     + ", requireConfirmation: " + mRequireConfirmation
150                     + ", user: " + getTargetUserId());
151 
152             if (authenticated) {
153                 mAlreadyDone = true;
154 
155                 if (listener != null) {
156                     vibrateSuccess();
157                 }
158                 result = true;
159                 if (shouldFrameworkHandleLockout()) {
160                     resetFailedAttempts();
161                 }
162                 onStop();
163 
164                 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) {
193                     vibrateError();
194                 }
195 
196                 // Allow system-defined limit of number of attempts before giving up
197                 final int lockoutMode = handleFailedAttempt();
198                 if (lockoutMode != LOCKOUT_NONE && shouldFrameworkHandleLockout()) {
199                     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;
205                     onError(getHalDeviceId(), errorCode, 0 /* vendorCode */);
206                 } 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                     }
218                 }
219                 result = lockoutMode != LOCKOUT_NONE; // in a lockout mode
220             }
221         } catch (RemoteException e) {
222             Slog.e(getLogTag(), "Remote exception", e);
223             result = true;
224         }
225         return result;
226     }
227 
228     /**
229      * Start authentication
230      */
231     @Override
start()232     public int start() {
233         mStarted = true;
234         onStart();
235         try {
236             mStartTimeMs = System.currentTimeMillis();
237             final int result = getDaemonWrapper().authenticate(mOpId, getGroupId());
238             if (result != 0) {
239                 Slog.w(getLogTag(), "startAuthentication failed, result=" + result);
240                 mMetricsLogger.histogram(mConstants.tagAuthStartError(), result);
241                 onError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
242                         0 /* vendorCode */);
243                 return result;
244             }
245             if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() + " is authenticating...");
246         } catch (RemoteException e) {
247             Slog.e(getLogTag(), "startAuthentication failed", e);
248             return ERROR_ESRCH;
249         }
250         return 0; // success
251     }
252 
253     @Override
stop(boolean initiatedByClient)254     public int stop(boolean initiatedByClient) {
255         if (mAlreadyCancelled) {
256             Slog.w(getLogTag(), "stopAuthentication: already cancelled!");
257             return 0;
258         }
259 
260         mStarted = false;
261 
262         onStop();
263 
264         try {
265             final int result = getDaemonWrapper().cancel();
266             if (result != 0) {
267                 Slog.w(getLogTag(), "stopAuthentication failed, result=" + result);
268                 return result;
269             }
270             if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() +
271                     " is no longer authenticating");
272         } catch (RemoteException e) {
273             Slog.e(getLogTag(), "stopAuthentication failed", e);
274             return ERROR_ESRCH;
275         }
276 
277         mAlreadyCancelled = true;
278         return 0; // success
279     }
280 
281     @Override
onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining)282     public boolean onEnrollResult(BiometricAuthenticator.Identifier identifier,
283             int remaining) {
284         if (DEBUG) Slog.w(getLogTag(), "onEnrollResult() called for authenticate!");
285         return true; // Invalid for Authenticate
286     }
287 
288     @Override
onRemoved(BiometricAuthenticator.Identifier identifier, int remaining)289     public boolean onRemoved(BiometricAuthenticator.Identifier identifier, int remaining) {
290         if (DEBUG) Slog.w(getLogTag(), "onRemoved() called for authenticate!");
291         return true; // Invalid for Authenticate
292     }
293 
294     @Override
onEnumerationResult(BiometricAuthenticator.Identifier identifier, int remaining)295     public boolean onEnumerationResult(BiometricAuthenticator.Identifier identifier,
296             int remaining) {
297         if (DEBUG) Slog.w(getLogTag(), "onEnumerationResult() called for authenticate!");
298         return true; // Invalid for Authenticate
299     }
300 }
301