1 /* 2 * Copyright (C) 2022 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.sensors; 18 19 import static com.android.server.biometrics.sensors.AuthResultCoordinator.AUTHENTICATOR_PERMANENT_LOCKED; 20 import static com.android.server.biometrics.sensors.AuthResultCoordinator.AUTHENTICATOR_TIMED_LOCKED; 21 import static com.android.server.biometrics.sensors.AuthResultCoordinator.AUTHENTICATOR_UNLOCKED; 22 23 import android.hardware.biometrics.BiometricManager.Authenticators; 24 import android.os.SystemClock; 25 import android.util.Slog; 26 27 import com.android.internal.annotations.VisibleForTesting; 28 29 import java.time.Clock; 30 import java.util.Arrays; 31 import java.util.HashSet; 32 import java.util.Map; 33 import java.util.Set; 34 35 /** 36 * Coordinates lockout counter enforcement for all types of biometric strengths across all users. 37 * 38 * This class is not thread-safe. In general, all calls to this class should be made on the same 39 * handler to ensure no collisions. 40 */ 41 public class AuthSessionCoordinator implements AuthSessionListener { 42 private static final String TAG = "AuthSessionCoordinator"; 43 44 private final Set<Integer> mAuthOperations; 45 private final MultiBiometricLockoutState mMultiBiometricLockoutState; 46 private final RingBuffer mRingBuffer; 47 48 private int mUserId; 49 private boolean mIsAuthenticating; 50 private AuthResultCoordinator mAuthResultCoordinator; 51 AuthSessionCoordinator()52 public AuthSessionCoordinator() { 53 this(SystemClock.elapsedRealtimeClock()); 54 } 55 56 @VisibleForTesting AuthSessionCoordinator(Clock clock)57 AuthSessionCoordinator(Clock clock) { 58 mAuthOperations = new HashSet<>(); 59 mAuthResultCoordinator = new AuthResultCoordinator(); 60 mMultiBiometricLockoutState = new MultiBiometricLockoutState(clock); 61 mRingBuffer = new RingBuffer(100); 62 } 63 64 /** 65 * A Call indicating that an auth session has started 66 */ onAuthSessionStarted(int userId)67 void onAuthSessionStarted(int userId) { 68 mAuthOperations.clear(); 69 mUserId = userId; 70 mIsAuthenticating = true; 71 mAuthResultCoordinator = new AuthResultCoordinator(); 72 mRingBuffer.addApiCall("internal : onAuthSessionStarted(" + userId + ")"); 73 } 74 75 /** 76 * Ends the current auth session and updates the lockout state. 77 * 78 * This can happen two ways. 79 * 1. Manually calling this API 80 * 2. If authStartedFor() was called, and any authentication attempts finish. 81 */ endAuthSession()82 void endAuthSession() { 83 // User unlocks can also unlock timed lockout Authenticator.Types 84 final Map<Integer, Integer> result = mAuthResultCoordinator.getResult(); 85 for (int authenticator : Arrays.asList(Authenticators.BIOMETRIC_CONVENIENCE, 86 Authenticators.BIOMETRIC_WEAK, Authenticators.BIOMETRIC_STRONG)) { 87 final Integer value = result.get(authenticator); 88 if ((value & AUTHENTICATOR_UNLOCKED) == AUTHENTICATOR_UNLOCKED) { 89 mMultiBiometricLockoutState.clearPermanentLockOut(mUserId, authenticator); 90 mMultiBiometricLockoutState.clearTimedLockout(mUserId, authenticator); 91 } else if ((value & AUTHENTICATOR_PERMANENT_LOCKED) == AUTHENTICATOR_PERMANENT_LOCKED) { 92 mMultiBiometricLockoutState.setPermanentLockOut(mUserId, authenticator); 93 } else if ((value & AUTHENTICATOR_TIMED_LOCKED) == AUTHENTICATOR_TIMED_LOCKED) { 94 mMultiBiometricLockoutState.setTimedLockout(mUserId, authenticator); 95 } 96 } 97 98 if (mAuthOperations.isEmpty()) { 99 mRingBuffer.addApiCall("internal : onAuthSessionEnded(" + mUserId + ")"); 100 clearSession(); 101 } 102 } 103 clearSession()104 private void clearSession() { 105 mIsAuthenticating = false; 106 mAuthOperations.clear(); 107 } 108 109 /** 110 * Returns the current lockout state for a given user/strength. 111 */ 112 @LockoutTracker.LockoutMode getLockoutStateFor(int userId, @Authenticators.Types int strength)113 public int getLockoutStateFor(int userId, @Authenticators.Types int strength) { 114 return mMultiBiometricLockoutState.getLockoutState(userId, strength); 115 } 116 117 @Override authStartedFor(int userId, int sensorId, long requestId)118 public void authStartedFor(int userId, int sensorId, long requestId) { 119 mRingBuffer.addApiCall( 120 "authStartedFor(userId=" + userId + ", sensorId=" + sensorId + ", requestId=" 121 + requestId + ")"); 122 if (!mIsAuthenticating) { 123 onAuthSessionStarted(userId); 124 } 125 126 if (mAuthOperations.contains(sensorId)) { 127 Slog.e(TAG, "Error, authStartedFor(" + sensorId + ") without being finished"); 128 return; 129 } 130 131 if (mUserId != userId) { 132 Slog.e(TAG, "Error authStartedFor(" + userId + ") Incorrect userId, expected" + mUserId 133 + ", ignoring..."); 134 return; 135 } 136 137 mAuthOperations.add(sensorId); 138 } 139 140 @Override lockedOutFor(int userId, @Authenticators.Types int biometricStrength, int sensorId, long requestId)141 public void lockedOutFor(int userId, @Authenticators.Types int biometricStrength, int sensorId, 142 long requestId) { 143 final String lockedOutStr = 144 "lockOutFor(userId=" + userId + ", biometricStrength=" + biometricStrength 145 + ", sensorId=" + sensorId + ", requestId=" + requestId + ")"; 146 mRingBuffer.addApiCall(lockedOutStr); 147 mAuthResultCoordinator.lockedOutFor(biometricStrength); 148 attemptToFinish(userId, sensorId, lockedOutStr); 149 } 150 151 @Override lockOutTimed(int userId, @Authenticators.Types int biometricStrength, int sensorId, long time, long requestId)152 public void lockOutTimed(int userId, @Authenticators.Types int biometricStrength, int sensorId, 153 long time, long requestId) { 154 final String lockedOutStr = 155 "lockOutTimedFor(userId=" + userId + ", biometricStrength=" + biometricStrength 156 + ", sensorId=" + sensorId + "time=" + time + ", requestId=" + requestId 157 + ")"; 158 mRingBuffer.addApiCall(lockedOutStr); 159 mAuthResultCoordinator.lockOutTimed(biometricStrength); 160 attemptToFinish(userId, sensorId, lockedOutStr); 161 } 162 163 @Override authEndedFor(int userId, @Authenticators.Types int biometricStrength, int sensorId, long requestId, boolean wasSuccessful)164 public void authEndedFor(int userId, @Authenticators.Types int biometricStrength, int sensorId, 165 long requestId, boolean wasSuccessful) { 166 final String authEndedStr = 167 "authEndedFor(userId=" + userId + " ,biometricStrength=" + biometricStrength 168 + ", sensorId=" + sensorId + ", requestId=" + requestId + ", wasSuccessful=" 169 + wasSuccessful + ")"; 170 mRingBuffer.addApiCall(authEndedStr); 171 if (wasSuccessful) { 172 mAuthResultCoordinator.authenticatedFor(biometricStrength); 173 } 174 attemptToFinish(userId, sensorId, authEndedStr); 175 } 176 177 @Override resetLockoutFor(int userId, @Authenticators.Types int biometricStrength, long requestId)178 public void resetLockoutFor(int userId, @Authenticators.Types int biometricStrength, 179 long requestId) { 180 final String resetLockStr = 181 "resetLockoutFor(userId=" + userId + " ,biometricStrength=" + biometricStrength 182 + ", requestId=" + requestId + ")"; 183 mRingBuffer.addApiCall(resetLockStr); 184 if (biometricStrength == Authenticators.BIOMETRIC_STRONG) { 185 clearSession(); 186 } else { 187 // Lockouts cannot be reset by non-strong biometrics 188 return; 189 } 190 mMultiBiometricLockoutState.clearPermanentLockOut(userId, biometricStrength); 191 mMultiBiometricLockoutState.clearTimedLockout(userId, biometricStrength); 192 } 193 attemptToFinish(int userId, int sensorId, String description)194 private void attemptToFinish(int userId, int sensorId, String description) { 195 boolean didFail = false; 196 if (!mAuthOperations.contains(sensorId)) { 197 Slog.e(TAG, "Error unable to find auth operation : " + description); 198 didFail = true; 199 } 200 if (userId != mUserId) { 201 Slog.e(TAG, "Error mismatched userId, expected=" + mUserId + " for " + description); 202 didFail = true; 203 } 204 if (didFail) { 205 return; 206 } 207 mAuthOperations.remove(sensorId); 208 if (mIsAuthenticating) { 209 endAuthSession(); 210 } 211 } 212 213 /** 214 * Returns a string representation of the past N API calls as well as the 215 * permanent and timed lockout states for each user's authenticators. 216 */ 217 @Override toString()218 public String toString() { 219 return mRingBuffer + "\n" + mMultiBiometricLockoutState; 220 } 221 222 private static class RingBuffer { 223 private final String[] mApiCalls; 224 private final int mSize; 225 private int mCurr; 226 private int mApiCallNumber; 227 RingBuffer(int size)228 RingBuffer(int size) { 229 if (size <= 0) { 230 Slog.wtf(TAG, "Cannot initialize ring buffer of size: " + size); 231 } 232 mApiCalls = new String[size]; 233 mCurr = 0; 234 mSize = size; 235 mApiCallNumber = 0; 236 } 237 addApiCall(String str)238 void addApiCall(String str) { 239 mApiCalls[mCurr] = str; 240 mCurr++; 241 mCurr %= mSize; 242 mApiCallNumber++; 243 } 244 245 @Override toString()246 public String toString() { 247 String buffer = ""; 248 int apiCall = mApiCallNumber > mSize ? mApiCallNumber - mSize : 0; 249 for (int i = 0; i < mSize; i++) { 250 final int location = (mCurr + i) % mSize; 251 if (mApiCalls[location] != null) { 252 buffer += String.format("#%-5d %s\n", apiCall++, mApiCalls[location]); 253 } 254 } 255 return buffer; 256 } 257 } 258 } 259