1 /* 2 * Copyright (C) 2020 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 static android.hardware.biometrics.BiometricAuthenticator.TYPE_CREDENTIAL; 20 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; 21 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; 22 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS; 23 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE; 24 25 import android.annotation.IntDef; 26 import android.annotation.NonNull; 27 import android.app.admin.DevicePolicyManager; 28 import android.app.trust.ITrustManager; 29 import android.content.Context; 30 import android.hardware.SensorPrivacyManager; 31 import android.hardware.biometrics.BiometricAuthenticator; 32 import android.hardware.biometrics.BiometricManager; 33 import android.hardware.biometrics.PromptInfo; 34 import android.os.RemoteException; 35 import android.util.Pair; 36 import android.util.Slog; 37 38 import com.android.server.biometrics.sensors.LockoutTracker; 39 40 import java.lang.annotation.Retention; 41 import java.lang.annotation.RetentionPolicy; 42 import java.util.ArrayList; 43 import java.util.List; 44 45 /** 46 * Class representing the calling client's request. Additionally, derives/calculates 47 * preliminary info that would be useful in helping serve this request. Note that generating 48 * the PreAuthInfo should not change any sensor state. 49 */ 50 class PreAuthInfo { 51 private static final String TAG = "BiometricService/PreAuthInfo"; 52 53 static final int AUTHENTICATOR_OK = 1; 54 static final int BIOMETRIC_NO_HARDWARE = 2; 55 static final int BIOMETRIC_DISABLED_BY_DEVICE_POLICY = 3; 56 static final int BIOMETRIC_INSUFFICIENT_STRENGTH = 4; 57 static final int BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE = 5; 58 static final int BIOMETRIC_HARDWARE_NOT_DETECTED = 6; 59 static final int BIOMETRIC_NOT_ENROLLED = 7; 60 static final int BIOMETRIC_NOT_ENABLED_FOR_APPS = 8; 61 static final int CREDENTIAL_NOT_ENROLLED = 9; 62 static final int BIOMETRIC_LOCKOUT_TIMED = 10; 63 static final int BIOMETRIC_LOCKOUT_PERMANENT = 11; 64 static final int BIOMETRIC_SENSOR_PRIVACY_ENABLED = 12; 65 @IntDef({AUTHENTICATOR_OK, 66 BIOMETRIC_NO_HARDWARE, 67 BIOMETRIC_DISABLED_BY_DEVICE_POLICY, 68 BIOMETRIC_INSUFFICIENT_STRENGTH, 69 BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE, 70 BIOMETRIC_HARDWARE_NOT_DETECTED, 71 BIOMETRIC_NOT_ENROLLED, 72 BIOMETRIC_NOT_ENABLED_FOR_APPS, 73 CREDENTIAL_NOT_ENROLLED, 74 BIOMETRIC_LOCKOUT_TIMED, 75 BIOMETRIC_LOCKOUT_PERMANENT, 76 BIOMETRIC_SENSOR_PRIVACY_ENABLED}) 77 @Retention(RetentionPolicy.SOURCE) 78 @interface AuthenticatorStatus {} 79 80 private final boolean mBiometricRequested; 81 private final int mBiometricStrengthRequested; 82 83 final boolean credentialRequested; 84 // Sensors that can be used for this request (e.g. strong enough, enrolled, enabled). 85 final List<BiometricSensor> eligibleSensors; 86 // Sensors that cannot be used for this request. Pair<BiometricSensor, AuthenticatorStatus> 87 final List<Pair<BiometricSensor, Integer>> ineligibleSensors; 88 final boolean credentialAvailable; 89 final boolean confirmationRequested; 90 final boolean ignoreEnrollmentState; 91 final int userId; 92 final Context context; 93 create(ITrustManager trustManager, DevicePolicyManager devicePolicyManager, BiometricService.SettingObserver settingObserver, List<BiometricSensor> sensors, int userId, PromptInfo promptInfo, String opPackageName, boolean checkDevicePolicyManager, Context context)94 static PreAuthInfo create(ITrustManager trustManager, 95 DevicePolicyManager devicePolicyManager, 96 BiometricService.SettingObserver settingObserver, 97 List<BiometricSensor> sensors, 98 int userId, PromptInfo promptInfo, String opPackageName, 99 boolean checkDevicePolicyManager, Context context) 100 throws RemoteException { 101 102 final boolean confirmationRequested = promptInfo.isConfirmationRequested(); 103 final boolean biometricRequested = Utils.isBiometricRequested(promptInfo); 104 final int requestedStrength = Utils.getPublicBiometricStrength(promptInfo); 105 final boolean credentialRequested = Utils.isCredentialRequested(promptInfo); 106 107 final boolean credentialAvailable = trustManager.isDeviceSecure(userId, 108 context.getAssociatedDisplayId()); 109 110 // Assuming that biometric authenticators are listed in priority-order, the rest of this 111 // function will attempt to find the first authenticator that's as strong or stronger than 112 // the requested strength, available, enrolled, and enabled. The tricky part is returning 113 // the correct error. Error strings that are modality-specific should also respect the 114 // priority-order. 115 116 final List<BiometricSensor> eligibleSensors = new ArrayList<>(); 117 final List<Pair<BiometricSensor, Integer>> ineligibleSensors = new ArrayList<>(); 118 119 if (biometricRequested) { 120 for (BiometricSensor sensor : sensors) { 121 122 @AuthenticatorStatus int status = getStatusForBiometricAuthenticator( 123 devicePolicyManager, settingObserver, sensor, userId, opPackageName, 124 checkDevicePolicyManager, requestedStrength, 125 promptInfo.getAllowedSensorIds(), 126 promptInfo.isIgnoreEnrollmentState(), 127 context); 128 129 Slog.d(TAG, "Package: " + opPackageName 130 + " Sensor ID: " + sensor.id 131 + " Modality: " + sensor.modality 132 + " Status: " + status); 133 134 // A sensor with privacy enabled will still be eligible to 135 // authenticate with biometric prompt. This is so the framework can display 136 // a sensor privacy error message to users after briefly showing the 137 // Biometric Prompt. 138 // 139 // Note: if only a certain sensor is required and the privacy is enabled, 140 // canAuthenticate() will return false. 141 if (status == AUTHENTICATOR_OK || status == BIOMETRIC_SENSOR_PRIVACY_ENABLED) { 142 eligibleSensors.add(sensor); 143 } else { 144 ineligibleSensors.add(new Pair<>(sensor, status)); 145 } 146 } 147 } 148 149 return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested, 150 eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested, 151 promptInfo.isIgnoreEnrollmentState(), userId, context); 152 } 153 154 /** 155 * Returns the status of the authenticator, with errors returned in a specific priority order. 156 * For example, {@link #BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE} is only returned 157 * if it has enrollments, and is enabled for apps. 158 * 159 * @return @AuthenticatorStatus 160 */ getStatusForBiometricAuthenticator( DevicePolicyManager devicePolicyManager, BiometricService.SettingObserver settingObserver, BiometricSensor sensor, int userId, String opPackageName, boolean checkDevicePolicyManager, int requestedStrength, @NonNull List<Integer> requestedSensorIds, boolean ignoreEnrollmentState, Context context)161 private static @AuthenticatorStatus int getStatusForBiometricAuthenticator( 162 DevicePolicyManager devicePolicyManager, 163 BiometricService.SettingObserver settingObserver, 164 BiometricSensor sensor, int userId, String opPackageName, 165 boolean checkDevicePolicyManager, int requestedStrength, 166 @NonNull List<Integer> requestedSensorIds, 167 boolean ignoreEnrollmentState, Context context) { 168 169 if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) { 170 return BIOMETRIC_NO_HARDWARE; 171 } 172 173 final boolean wasStrongEnough = 174 Utils.isAtLeastStrength(sensor.oemStrength, requestedStrength); 175 final boolean isStrongEnough = 176 Utils.isAtLeastStrength(sensor.getCurrentStrength(), requestedStrength); 177 178 if (wasStrongEnough && !isStrongEnough) { 179 return BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE; 180 } else if (!wasStrongEnough) { 181 return BIOMETRIC_INSUFFICIENT_STRENGTH; 182 } 183 184 try { 185 if (!sensor.impl.isHardwareDetected(opPackageName)) { 186 return BIOMETRIC_HARDWARE_NOT_DETECTED; 187 } 188 189 if (!sensor.impl.hasEnrolledTemplates(userId, opPackageName) 190 && !ignoreEnrollmentState) { 191 return BIOMETRIC_NOT_ENROLLED; 192 } 193 final SensorPrivacyManager sensorPrivacyManager = context 194 .getSystemService(SensorPrivacyManager.class); 195 196 if (sensorPrivacyManager != null && sensor.modality == TYPE_FACE) { 197 if (sensorPrivacyManager 198 .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId)) { 199 return BIOMETRIC_SENSOR_PRIVACY_ENABLED; 200 } 201 } 202 203 204 final @LockoutTracker.LockoutMode int lockoutMode = 205 sensor.impl.getLockoutModeForUser(userId); 206 if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) { 207 return BIOMETRIC_LOCKOUT_TIMED; 208 } else if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) { 209 return BIOMETRIC_LOCKOUT_PERMANENT; 210 } 211 } catch (RemoteException e) { 212 return BIOMETRIC_HARDWARE_NOT_DETECTED; 213 } 214 215 if (!isEnabledForApp(settingObserver, sensor.modality, userId)) { 216 return BIOMETRIC_NOT_ENABLED_FOR_APPS; 217 } 218 219 if (checkDevicePolicyManager) { 220 if (isBiometricDisabledByDevicePolicy(devicePolicyManager, sensor.modality, userId)) { 221 return BIOMETRIC_DISABLED_BY_DEVICE_POLICY; 222 } 223 } 224 225 return AUTHENTICATOR_OK; 226 } 227 isEnabledForApp(BiometricService.SettingObserver settingObserver, @BiometricAuthenticator.Modality int modality, int userId)228 private static boolean isEnabledForApp(BiometricService.SettingObserver settingObserver, 229 @BiometricAuthenticator.Modality int modality, int userId) { 230 return settingObserver.getEnabledForApps(userId); 231 } 232 isBiometricDisabledByDevicePolicy( DevicePolicyManager devicePolicyManager, @BiometricAuthenticator.Modality int modality, int effectiveUserId)233 private static boolean isBiometricDisabledByDevicePolicy( 234 DevicePolicyManager devicePolicyManager, @BiometricAuthenticator.Modality int modality, 235 int effectiveUserId) { 236 final int biometricToCheck = mapModalityToDevicePolicyType(modality); 237 if (biometricToCheck == DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE) { 238 throw new IllegalStateException("Modality unknown to devicePolicyManager: " + modality); 239 } 240 final int devicePolicyDisabledFeatures = 241 devicePolicyManager.getKeyguardDisabledFeatures(null, effectiveUserId); 242 final boolean isBiometricDisabled = 243 (biometricToCheck & devicePolicyDisabledFeatures) != 0; 244 Slog.w(TAG, "isBiometricDisabledByDevicePolicy(" + modality + "," + effectiveUserId 245 + ")=" + isBiometricDisabled); 246 return isBiometricDisabled; 247 } 248 249 /** 250 * @param modality one of {@link BiometricAuthenticator#TYPE_FINGERPRINT}, 251 * {@link BiometricAuthenticator#TYPE_IRIS} or {@link BiometricAuthenticator#TYPE_FACE} 252 * @return 253 */ mapModalityToDevicePolicyType(int modality)254 private static int mapModalityToDevicePolicyType(int modality) { 255 switch (modality) { 256 case TYPE_FINGERPRINT: 257 return DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT; 258 case TYPE_IRIS: 259 return DevicePolicyManager.KEYGUARD_DISABLE_IRIS; 260 case TYPE_FACE: 261 return DevicePolicyManager.KEYGUARD_DISABLE_FACE; 262 default: 263 Slog.e(TAG, "Error modality=" + modality); 264 return DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE; 265 } 266 } 267 PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested, boolean credentialRequested, List<BiometricSensor> eligibleSensors, List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable, boolean confirmationRequested, boolean ignoreEnrollmentState, int userId, Context context)268 private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested, 269 boolean credentialRequested, List<BiometricSensor> eligibleSensors, 270 List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable, 271 boolean confirmationRequested, boolean ignoreEnrollmentState, int userId, 272 Context context) { 273 mBiometricRequested = biometricRequested; 274 mBiometricStrengthRequested = biometricStrengthRequested; 275 this.credentialRequested = credentialRequested; 276 277 this.eligibleSensors = eligibleSensors; 278 this.ineligibleSensors = ineligibleSensors; 279 this.credentialAvailable = credentialAvailable; 280 this.confirmationRequested = confirmationRequested; 281 this.ignoreEnrollmentState = ignoreEnrollmentState; 282 this.userId = userId; 283 this.context = context; 284 } 285 calculateErrorByPriority()286 private Pair<BiometricSensor, Integer> calculateErrorByPriority() { 287 // If the caller requested STRONG, and the device contains both STRONG and non-STRONG 288 // sensors, prioritize BIOMETRIC_NOT_ENROLLED over the weak sensor's 289 // BIOMETRIC_INSUFFICIENT_STRENGTH error. Pretty sure we can always prioritize 290 // BIOMETRIC_NOT_ENROLLED over any other error (unless of course its calculation is 291 // wrong, in which case we should fix that instead). 292 for (Pair<BiometricSensor, Integer> pair : ineligibleSensors) { 293 if (pair.second == BIOMETRIC_NOT_ENROLLED) { 294 return pair; 295 } 296 } 297 298 return ineligibleSensors.get(0); 299 } 300 301 /** 302 * With {@link PreAuthInfo} generated with the requested authenticators from the public API 303 * surface, combined with the actual sensor/credential and user/system settings, calculate the 304 * internal {@link AuthenticatorStatus} that should be returned to the client. Note that this 305 * will need to be converted into the public API constant. 306 * @return Pair<Modality, Error> with error being the internal {@link AuthenticatorStatus} code 307 */ getInternalStatus()308 private Pair<Integer, Integer> getInternalStatus() { 309 @AuthenticatorStatus final int status; 310 @BiometricAuthenticator.Modality int modality = TYPE_NONE; 311 312 final SensorPrivacyManager sensorPrivacyManager = context 313 .getSystemService(SensorPrivacyManager.class); 314 315 boolean cameraPrivacyEnabled = false; 316 if (sensorPrivacyManager != null) { 317 cameraPrivacyEnabled = sensorPrivacyManager 318 .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId); 319 } 320 321 if (mBiometricRequested && credentialRequested) { 322 if (credentialAvailable || !eligibleSensors.isEmpty()) { 323 for (BiometricSensor sensor : eligibleSensors) { 324 modality |= sensor.modality; 325 } 326 327 if (credentialAvailable) { 328 modality |= TYPE_CREDENTIAL; 329 status = AUTHENTICATOR_OK; 330 } else if (modality == TYPE_FACE && cameraPrivacyEnabled) { 331 // If the only modality requested is face, credential is unavailable, 332 // and the face sensor privacy is enabled then return 333 // BIOMETRIC_SENSOR_PRIVACY_ENABLED. 334 // 335 // Note: This sensor will still be eligible for calls to authenticate. 336 status = BIOMETRIC_SENSOR_PRIVACY_ENABLED; 337 } else { 338 status = AUTHENTICATOR_OK; 339 } 340 } else { 341 // Pick the first sensor error if it exists 342 if (!ineligibleSensors.isEmpty()) { 343 final Pair<BiometricSensor, Integer> pair = calculateErrorByPriority(); 344 modality |= pair.first.modality; 345 status = pair.second; 346 } else { 347 modality |= TYPE_CREDENTIAL; 348 status = CREDENTIAL_NOT_ENROLLED; 349 } 350 } 351 } else if (mBiometricRequested) { 352 if (!eligibleSensors.isEmpty()) { 353 for (BiometricSensor sensor : eligibleSensors) { 354 modality |= sensor.modality; 355 } 356 if (modality == TYPE_FACE && cameraPrivacyEnabled) { 357 // If the only modality requested is face and the privacy is enabled 358 // then return BIOMETRIC_SENSOR_PRIVACY_ENABLED. 359 // 360 // Note: This sensor will still be eligible for calls to authenticate. 361 status = BIOMETRIC_SENSOR_PRIVACY_ENABLED; 362 } else { 363 status = AUTHENTICATOR_OK; 364 } 365 } else { 366 // Pick the first sensor error if it exists 367 if (!ineligibleSensors.isEmpty()) { 368 final Pair<BiometricSensor, Integer> pair = calculateErrorByPriority(); 369 modality |= pair.first.modality; 370 status = pair.second; 371 } else { 372 modality |= TYPE_NONE; 373 status = BIOMETRIC_NO_HARDWARE; 374 } 375 } 376 } else if (credentialRequested) { 377 modality |= TYPE_CREDENTIAL; 378 status = credentialAvailable ? AUTHENTICATOR_OK : CREDENTIAL_NOT_ENROLLED; 379 } else { 380 // This should not be possible via the public API surface and is here mainly for 381 // "correctness". An exception should have been thrown before getting here. 382 Slog.e(TAG, "No authenticators requested"); 383 status = BIOMETRIC_NO_HARDWARE; 384 } 385 Slog.d(TAG, "getCanAuthenticateInternal Modality: " + modality 386 + " AuthenticatorStatus: " + status); 387 388 return new Pair<>(modality, status); 389 } 390 391 /** 392 * @return public BiometricManager result for the current request. 393 */ getCanAuthenticateResult()394 @BiometricManager.BiometricError int getCanAuthenticateResult() { 395 // TODO: Convert this directly 396 return Utils.biometricConstantsToBiometricManager( 397 Utils.authenticatorStatusToBiometricConstant( 398 getInternalStatus().second)); 399 } 400 401 /** 402 * For the given request, generate the appropriate reason why authentication cannot be started. 403 * Note that for some errors, modality is intentionally cleared. 404 * @return Pair<Modality, Error> with modality being filtered if necessary, and error 405 * being one of the public {@link android.hardware.biometrics.BiometricConstants} codes. 406 */ getPreAuthenticateStatus()407 Pair<Integer, Integer> getPreAuthenticateStatus() { 408 final Pair<Integer, Integer> internalStatus = getInternalStatus(); 409 410 final int publicError = Utils.authenticatorStatusToBiometricConstant(internalStatus.second); 411 int modality = internalStatus.first; 412 switch (internalStatus.second) { 413 case AUTHENTICATOR_OK: 414 case BIOMETRIC_NO_HARDWARE: 415 case BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE: 416 case BIOMETRIC_HARDWARE_NOT_DETECTED: 417 case BIOMETRIC_NOT_ENROLLED: 418 case CREDENTIAL_NOT_ENROLLED: 419 case BIOMETRIC_LOCKOUT_TIMED: 420 case BIOMETRIC_LOCKOUT_PERMANENT: 421 case BIOMETRIC_SENSOR_PRIVACY_ENABLED: 422 break; 423 424 case BIOMETRIC_DISABLED_BY_DEVICE_POLICY: 425 case BIOMETRIC_INSUFFICIENT_STRENGTH: 426 case BIOMETRIC_NOT_ENABLED_FOR_APPS: 427 default: 428 modality = TYPE_NONE; 429 break; 430 } 431 432 return new Pair<>(modality, publicError); 433 } 434 435 /** 436 * @return true if SystemUI should show the credential UI. 437 */ shouldShowCredential()438 boolean shouldShowCredential() { 439 return credentialRequested && credentialAvailable; 440 } 441 442 /** 443 * @return bitmask representing the modalities that are running or could be running for the 444 * current session. 445 */ getEligibleModalities()446 @BiometricAuthenticator.Modality int getEligibleModalities() { 447 @BiometricAuthenticator.Modality int modalities = 0; 448 for (BiometricSensor sensor : eligibleSensors) { 449 modalities |= sensor.modality; 450 } 451 452 if (credentialRequested && credentialAvailable) { 453 modalities |= TYPE_CREDENTIAL; 454 } 455 return modalities; 456 } 457 numSensorsWaitingForCookie()458 int numSensorsWaitingForCookie() { 459 int numWaiting = 0; 460 for (BiometricSensor sensor : eligibleSensors) { 461 if (sensor.getSensorState() == BiometricSensor.STATE_WAITING_FOR_COOKIE) { 462 Slog.d(TAG, "Sensor ID: " + sensor.id 463 + " Waiting for cookie: " + sensor.getCookie()); 464 numWaiting++; 465 } 466 } 467 return numWaiting; 468 } 469 470 @Override toString()471 public String toString() { 472 StringBuilder string = new StringBuilder( 473 "BiometricRequested: " + mBiometricRequested 474 + ", StrengthRequested: " + mBiometricStrengthRequested 475 + ", CredentialRequested: " + credentialRequested); 476 string.append(", Eligible:{"); 477 for (BiometricSensor sensor: eligibleSensors) { 478 string.append(sensor.id).append(" "); 479 } 480 string.append("}"); 481 482 string.append(", Ineligible:{"); 483 for (Pair<BiometricSensor, Integer> ineligible : ineligibleSensors) { 484 string.append(ineligible.first).append(":").append(ineligible.second).append(" "); 485 } 486 string.append("}"); 487 488 string.append(", CredentialAvailable: ").append(credentialAvailable); 489 string.append(", "); 490 return string.toString(); 491 } 492 } 493