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