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